{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Variables, números y texto\n",
    "\n",
    "Los cuatro tipos que vas a usar siempre, el error de sumar texto con número, y por qué 0,1 más 0,2 no da 0,3.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 3 de **Python desde cero**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/python-desde-cero/variables-y-tipos/\n",
    "\n",
    "Este es el cuaderno de **soluciones**. Trae el código de cada ejercicio, la\n",
    "explicación de la trampa y la respuesta del quiz. Si vienes del cuaderno de\n",
    "práctica sin haberlo intentado, vuelve 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Una variable es una etiqueta, no una caja"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hola! Vamos con lo que hay dentro de una variable, que es la base de todo lo\n",
    "demás 🌸\n",
    "\n",
    "Casi siempre te van a explicar que una variable es una caja donde guardas\n",
    "algo. Yo prefiero otra imagen, porque te va a ahorrar un dolor de cabeza más\n",
    "adelante: **una variable es una etiqueta que le pegas a un valor**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ciudad = 'Arequipa'\n",
    "ventas = 3\n",
    "promedio = 1250.75\n",
    "\n",
    "print(ciudad, ventas, promedio)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El signo igual no significa \"es igual a\" como en matemáticas. Significa\n",
    "\"desde ahora, este nombre apunta a este valor\". Por eso esto que parecería una\n",
    "locura funciona sin problema:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "contador = 10\n",
    "contador = contador + 5\n",
    "\n",
    "print(contador)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se lee de derecha a izquierda: agarra lo que hay en `contador`,\n",
    "súmale 5, y pega la etiqueta `contador` al resultado.\n",
    "\n",
    "### Cómo se nombran las variables\n",
    "\n",
    "Hay reglas que Python obliga y convenciones que obliga la comunidad. Las dos\n",
    "importan.\n",
    "\n",
    "Lo que Python no te deja: empezar con un número, usar espacios, usar guiones,\n",
    "o usar una palabra reservada como `class` o `for`.\n",
    "\n",
    "Lo que se acostumbra, y viene escrito en un documento que se llama PEP 8 (la\n",
    "guía de estilo oficial de Python):\n",
    "\n",
    "- 🐍 Todo en minúsculas y las palabras separadas con guion bajo:\n",
    "`total_ventas`, `fecha_de_corte`. Eso se llama snake_case.\n",
    "\n",
    "- 📝 Nombres que digan qué hay dentro. `df2` no dice nada;\n",
    "`ventas_limpias` sí.\n",
    "\n",
    "- 🚫 Nada de tildes ni ñ, aunque Python las acepte. Te van a dar problemas al\n",
    "compartir código.\n",
    "\n",
    "Te lo digo por experiencia: el código que escribes hoy lo lee otra persona\n",
    "dentro de tres meses, y muchas veces esa otra persona eres tú 😅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los cuatro tipos que vas a usar siempre"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python tiene muchos tipos, pero para datos son cuatro los que aparecen todo\n",
    "el tiempo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "unidades = 12                 # int:   numero entero\n",
    "monto = 1250.75               # float: numero con decimales\n",
    "cliente = 'Minimarket El Sol' # str:   texto\n",
    "pago = True                   # bool:  verdadero o falso\n",
    "\n",
    "print(type(unidades))\n",
    "print(type(monto))\n",
    "print(type(cliente))\n",
    "print(type(pago))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`type()` es la pregunta \"qué eres tú\" y la vas a usar más de lo que\n",
    "crees. Cuando algo no funcione y no entiendas por qué, imprime el tipo antes de\n",
    "seguir buscando 🔍\n",
    "\n",
    "Un detalle sobre `bool`: por dentro es un número. Eso parece un\n",
    "dato curioso y en realidad es un truco que vas a usar seguido."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(True + True)\n",
    "print(sum([True, False, True, True]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sumar una lista de verdaderos y falsos te da cuántos verdaderos hay. Cuando\n",
    "lleguemos a pandas, así vas a contar cuántas filas cumplen una condición."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error de la primera semana"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este te va a pasar. A todo el mundo le pasa, así que mejor que te pase aquí\n",
    "conmigo y no un viernes a las siete."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "unidades = '12'      # ojo: vino como texto, con comillas\n",
    "precio = 40\n",
    "\n",
    "print(unidades * precio)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ajá 😅\n",
    "\n",
    "Multiplicar un texto por un número no es un error para Python: es repetir el\n",
    "texto esa cantidad de veces. Así que te repitió \"12\" cuarenta veces y no te\n",
    "avisó de nada.\n",
    "\n",
    "**Ese es el peor tipo de error: el que no falla.** Un error que\n",
    "revienta lo ves; este te deja seguir trabajando con un resultado que no\n",
    "significa nada.\n",
    "\n",
    "Con la suma sí revienta, y menos mal:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    print(unidades + precio)\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "TypeError: can only concatenate str (not \"int\") to str\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léelo con calma que el mensaje es bueno: \"solo puedo concatenar texto con\n",
    "texto, y me diste un int\". O sea, Python vio un texto a la izquierda y asumió\n",
    "que querías pegar palabras.\n",
    "\n",
    "La solución es convertir:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "unidades = int('12')\n",
    "\n",
    "print(unidades + precio)\n",
    "print(type(unidades))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las tres conversiones que vas a usar son `int()`,\n",
    "`float()` y `str()`. Y una advertencia que te ahorra rato:\n",
    "`int()` con un texto que tenga decimales no funciona."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    int('40.5')\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "ValueError: invalid literal for int() with base 10: '40.5'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí hay que pasar por `float()` primero, o directamente usar\n",
    "`float()` si no necesitas un entero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Por qué 0,1 más 0,2 no da 0,3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este es de los que más desconciertan, y no es un bug de Python."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(0.1 + 0.2)\n",
    "print(0.1 + 0.2 == 0.3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No te asustes. Pasa en todos los lenguajes, y en Excel también (solo que\n",
    "Excel te lo esconde al mostrarte el número).\n",
    "\n",
    "La razón es que la computadora guarda los decimales en binario, y hay\n",
    "decimales que en binario no tienen representación exacta, igual que un tercio no\n",
    "la tiene en decimal: 0,3333... nunca termina.\n",
    "\n",
    "Lo que importa son las dos consecuencias prácticas:\n",
    "\n",
    "- ⚠️ **Nunca compares dos decimales con `==`.** Usa\n",
    "`round()` o compara la diferencia.\n",
    "\n",
    "- 💰 **Para dinero, redondea al final** y no en cada paso. Si el\n",
    "proyecto es contable de verdad, existe un tipo llamado `Decimal` que\n",
    "guarda los números exactos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(round(0.1 + 0.2, 2) == 0.3)\n",
    "\n",
    "from decimal import Decimal\n",
    "print(Decimal('0.1') + Decimal('0.2'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en que `Decimal` recibe el número **entre\n",
    "comillas**. Si le pasas `Decimal(0.1)` le estás dando el float\n",
    "que ya venía roto, y no arregla nada."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Texto, que es la mitad de tu trabajo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En datos reales, el 90% de lo sucio es texto: nombres con espacios de más,\n",
    "ciudades escritas de cuatro formas, números que vinieron como cadena. Así que\n",
    "estos métodos los vas a usar muchísimo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cliente = '  minimarket EL SOL  '\n",
    "\n",
    "print(cliente.strip())            # quita espacios de los extremos\n",
    "print(cliente.strip().title())    # Primera Letra De Cada Palabra\n",
    "print(cliente.strip().lower())    # todo minusculas\n",
    "print(cliente.replace('EL', 'el').strip())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un detalle importante: **estos métodos no modifican la variable, te\n",
    "devuelven una copia**. Si quieres conservar el cambio hay que\n",
    "reasignarlo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cliente.strip()\n",
    "print(repr(cliente))              # sigue con sus espacios\n",
    "\n",
    "cliente = cliente.strip()\n",
    "print(repr(cliente))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Usé `repr()` para que se vean las comillas y los espacios. En la\n",
    "primera línea siguen ahí, aunque hayamos llamado a `.strip()` justo\n",
    "antes: el método devolvió una copia limpia y nadie la guardó.\n",
    "\n",
    "Ese `repr()` va a ser tu mejor amigo depurando texto, porque un\n",
    "espacio de más es invisible con `print` normal 🔍\n",
    "\n",
    "Y el que más rinde de todos, `.split()`, que parte un texto en una\n",
    "lista:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fila = 'C0045;Arequipa;Minimarket;480.37'\n",
    "partes = fila.split(';')\n",
    "\n",
    "print(partes)\n",
    "print(partes[1])\n",
    "print(float(partes[3]) * 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso de arriba es, en miniatura, lo que hace pandas cuando lee un CSV 📊\n",
    "\n",
    "### f-strings: la forma de armar texto\n",
    "\n",
    "Antes había tres maneras de meter una variable dentro de un texto y todas\n",
    "eran incómodas. Desde Python 3.6 hay una sola que vale la pena, y se llama\n",
    "f-string: una `f` antes de la comilla y las variables entre llaves."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cliente = 'Minimarket El Sol'\n",
    "monto = 1250.756\n",
    "\n",
    "print(f'{cliente} compró S/{monto}')\n",
    "print(f'{cliente} compró S/{monto:.2f}')\n",
    "print(f'{cliente:>25} | S/{monto:>10.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esos dos puntos dentro de las llaves son el formato, y valen oro:\n",
    "\n",
    "- 💵 `:.2f` deja dos decimales. Para plata, siempre.\n",
    "\n",
    "- 📐 `:>10` alinea a la derecha en diez espacios, que es lo que\n",
    "hace que una tabla impresa se vea derecha.\n",
    "\n",
    "- 🔢 `:,` pone el separador de miles: `f'{1250000:,}'`\n",
    "sale `1,250,000`.\n",
    "\n",
    "El error que da la primera vez es olvidar la `f`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('{cliente} compró algo')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No revienta, simplemente te imprime las llaves tal cual. Si ves llaves en tu\n",
    "salida, te faltó la `f` 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## None, que no es cero ni vacío"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`None` es el valor que dice \"aquí no hay nada\". No es el 0, no es\n",
    "el texto vacío, es la ausencia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fecha_ultima_compra = None\n",
    "\n",
    "print(fecha_ultima_compra is None)\n",
    "print(fecha_ultima_compra == 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se compara con `is None` y no con `== None`. Es la\n",
    "convención de Python y hay razones técnicas, pero con que te acostumbres basta.\n",
    "\n",
    "Esto importa muchísimo en datos: cuando un cliente no tiene fecha de última\n",
    "compra, ese hueco no es un cero. Y tratarlo como cero te cambia el promedio y\n",
    "todas las conclusiones que salgan de ahí."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que Python considera vacío"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cualquier valor de Python se puede usar como si fuera verdadero o falso. Y\n",
    "cuáles son falsos hay que sabérselo, porque de aquí sale uno de los errores más\n",
    "caros que vas a cometer con datos 🕳️"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for valor in [0, 0.0, '', '0', ' ', [], {}, None, 'n/a']:\n",
    "    print(f'{repr(valor):<8} -> {bool(valor)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los cuatro que sorprenden 👀\n",
    "\n",
    "- 0️⃣ **El cero es falso.** El entero y el decimal.\n",
    "\n",
    "- 🔤 **El texto `'0'` es verdadero**, porque es un\n",
    "texto con un carácter dentro. El cero de mentira es verdadero y el de verdad es\n",
    "falso.\n",
    "\n",
    "- ␣ **Un espacio también es verdadero.** Una celda que parece\n",
    "vacía en el Excel y trae un espacio, para Python tiene contenido.\n",
    "\n",
    "- 📭 **La lista vacía y el diccionario vacío son falsos**, y eso\n",
    "sí es cómodo: `if resultados:` se lee \"si hay resultados\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El cero que desaparece del promedio"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora el error. Escribir `if descuento:` parece que dice \"si vino\n",
    "el descuento\". Lo que dice de verdad es \"si el descuento no es cero ni\n",
    "nulo\" 😖"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "descuentos = [15.0, 0.0, 8.5, None, 0.0, 12.0]\n",
    "\n",
    "con_if = [d for d in descuentos if d]\n",
    "con_is = [d for d in descuentos if d is not None]\n",
    "\n",
    "print('con if d            :', con_if, '->', len(con_if), 'clientes')\n",
    "print('con if d is not None:', con_is, '->', len(con_is), 'clientes')\n",
    "print('descuento promedio, primera forma :',\n",
    "      round(sum(con_if) / len(con_if), 2))\n",
    "print('descuento promedio, segunda forma :',\n",
    "      round(sum(con_is) / len(con_is), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira los dos promedios. No se parecen en nada 😳\n",
    "\n",
    "Los dos clientes con descuento **cero** son clientes reales a los\n",
    "que no se les dio descuento. El primer filtro los tira a la basura junto con el\n",
    "nulo, y el promedio sube de siete y pico a casi doce.\n",
    "\n",
    "Nadie va a notar ese error mirando el resultado, porque once y pico de\n",
    "descuento promedio es un número perfectamente creíble. Y estarías reportando\n",
    "un 67% más de lo que es 💸\n",
    "\n",
    "La regla, y es corta: **para preguntar si un dato vino,\n",
    "`is not None`. Para preguntar si un dato está vacío,\n",
    "`if`**. Son preguntas distintas y hay que elegir la que de\n",
    "verdad quieres."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## is no es lo mismo que =="
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya usaste `is None` arriba y no te expliqué por qué no es\n",
    "`== None`. Aquí va, con la sorpresa incluida:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "leido = int('2500')\n",
    "escrito = 2500\n",
    "\n",
    "print('valen lo mismo:', leido == escrito)\n",
    "print('son el mismo  :', leido is escrito)\n",
    "\n",
    "leido_chico = int('250')\n",
    "escrito_chico = 250\n",
    "\n",
    "print('con 250, valen lo mismo:', leido_chico == escrito_chico)\n",
    "print('con 250, son el mismo  :', leido_chico is escrito_chico)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los mismos dos números, dos respuestas distintas 🤯\n",
    "\n",
    "`==` pregunta **¿valen lo mismo?**.\n",
    "`is` pregunta **¿son exactamente el mismo objeto en la\n",
    "memoria?**, que es otra cosa.\n",
    "\n",
    "Y el motivo de que 250 sí y 2500 no: Python guarda de antemano los enteros\n",
    "pequeños, de -5 a 256, y los reparte a quien los pida. Los grandes los crea\n",
    "nuevos cada vez. Es un detalle interno, puede cambiar entre versiones, y por eso\n",
    "mismo **nunca compares números ni textos con `is`**.\n",
    "\n",
    "Con texto pasa lo mismo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ciudad_a = 'Lima'\n",
    "ciudad_c = ''.join(['Li', 'ma'])\n",
    "\n",
    "print('valen lo mismo:', ciudad_a == ciudad_c)\n",
    "print('son el mismo  :', ciudad_a is ciudad_c)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Entonces, ¿por qué `is None` sí? Porque `None` es\n",
    "**uno solo en todo el programa**. No hay dos. Preguntar si algo es\n",
    "ese objeto es la pregunta correcta, y por eso es la forma recomendada 💜\n",
    "\n",
    "Resumido: `is` solo con `None`, `True` y\n",
    "`False`. Para todo lo demás, `==`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Redondear tampoco es lo que crees"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('round(0.5) =', round(0.5))\n",
    "print('round(1.5) =', round(1.5))\n",
    "print('round(2.5) =', round(2.5))\n",
    "print('round(3.5) =', round(3.5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El 1,5 sube a 2 y el 2,5 baja a 2. No es un fallo 🧮\n",
    "\n",
    "Se llama redondeo bancario, y la regla es: **cuando el resto es\n",
    "exactamente medio, se redondea al par más cercano**. Existe justamente\n",
    "para que los medios no se vayan todos hacia arriba y el total se infle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "montos = [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5]\n",
    "\n",
    "print('redondeando uno a uno   :', sum(round(m) for m in montos))\n",
    "print('sumando y redondeando   :', round(sum(montos)))\n",
    "print('con el redondeo del cole:', sum(int(m + 0.5) for m in montos))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cincuenta contra cincuenta y cinco. Un 10% de diferencia en diez números, solo\n",
    "por cómo se redondea 💰\n",
    "\n",
    "Y ahí ves para qué sirve el redondeo bancario: la mitad de los medios suben y\n",
    "la otra mitad bajan, así que el total se queda donde tenía que quedarse. El\n",
    "redondeo que te enseñaron en el colegio los sube todos y desvía el total hacia\n",
    "arriba.\n",
    "\n",
    "Lo que importa en la práctica: **redondea al final, una sola vez**.\n",
    "Si redondeas en cada paso, cada redondeo mete su error y al final tienes un total\n",
    "que no cuadra con la suma de las partes. Y esa es la llamada de \"el reporte no\n",
    "cuadra por dos soles\" 📞"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Los cuatro tipos\n",
    "\n",
    "Crea una variable de cada tipo con datos de una venta e\n",
    "imprime el tipo de cada una en una sola línea."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "unidades = 8\n",
    "monto = 340.5\n",
    "canal = 'WhatsApp'\n",
    "pagado = False\n",
    "\n",
    "print(type(unidades), type(monto), type(canal), type(pagado))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "<class 'int'> <class 'float'> <class 'str'> <class 'bool'>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El total de una venta\n",
    "\n",
    "Con 8 unidades a S/34,90 cada una y 18% de IGV, imprime el\n",
    "total con dos decimales usando un f-string."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "unidades = 8\n",
    "precio = 34.90\n",
    "igv = 0.18\n",
    "\n",
    "total = unidades * precio * (1 + igv)\n",
    "print(f'Total: S/{total:.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Total: S/329.46\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en que redondeé solo al mostrar, no al calcular. Si redondeas en cada\n",
    "paso, los centavos se te van yendo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El texto que parecía número\n",
    "\n",
    "Te llega `'250'` como texto y\n",
    "`'1.5'` también. Multiplícalos y muestra el resultado con un\n",
    "decimal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "unidades = '250'          # como llega del sistema\n",
    "factor = '1.5'            # el multiplicador de la promocion\n",
    "\n",
    "resultado = int(unidades) * float(factor)\n",
    "print(f'{resultado:.1f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "375.0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ojo con hacer `int(factor)`: ahí revienta con un\n",
    "`ValueError`, porque `int()` no acepta un texto con\n",
    "punto decimal."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Limpiar un nombre de cliente\n",
    "\n",
    "De `'   BODEGA doña ROSA  '` saca\n",
    "`'Bodega Doña Rosa'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sucio = '   BODEGA doña ROSA  '\n",
    "limpio = sucio.strip().title()\n",
    "\n",
    "print(repr(limpio))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "'Bodega Doña Rosa'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Usé `repr()` a propósito: muestra las comillas, así compruebas que\n",
    "de verdad no quedaron espacios. Es mi truco favorito para depurar texto 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Partir una fila de CSV\n",
    "\n",
    "De `'2026-04-29,Cusco,Web,154.83'` imprime la\n",
    "ciudad y el monto multiplicado por 1,18."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fila = '2026-04-29,Cusco,Web,154.83'\n",
    "partes = fila.split(',')\n",
    "\n",
    "print(partes[1])\n",
    "print(f'{float(partes[3]) * 1.18:.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Cusco\n",
    "182.70\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. La comparación que falla\n",
    "\n",
    "Comprueba que `1.1 + 2.2` no es igual a\n",
    "`3.3`, y después arregla la comparación."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(1.1 + 2.2)                       # dos productos de S/1.10 y S/2.20\n",
    "print(1.1 + 2.2 == 3.3)\n",
    "print(round(1.1 + 2.2, 10) == 3.3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "3.3000000000000003\n",
    "False\n",
    "True\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Redondear a diez decimales suena exagerado y es justo el punto: el error del\n",
    "binario está mucho más allá, así que con redondear a cualquier cantidad\n",
    "razonable de decimales el problema desaparece."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Una tabla alineada\n",
    "\n",
    "Imprime tres líneas con nombre de ciudad y monto, de forma\n",
    "que los montos queden alineados a la derecha."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "datos = [('Lima', 12500.5), ('Arequipa', 980.25), ('Cusco', 145600.0)]\n",
    "\n",
    "for ciudad, monto in datos:\n",
    "    print(f'{ciudad:<12} S/{monto:>12,.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Lima         S/   12,500.50\n",
    "Arequipa     S/      980.25\n",
    "Cusco        S/  145,600.00\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `:<12` alinea a la izquierda y `:>12,.2f`\n",
    "alinea a la derecha con separador de miles y dos decimales. Con eso ya puedes\n",
    "imprimir un reporte decente sin ninguna librería."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Contar con booleanos\n",
    "\n",
    "Tienes una lista de montos. Cuenta cuántos superan los\n",
    "S/1.000 usando el truco de sumar booleanos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "montos = [480.37, 1250.0, 95.5, 3400.2, 780.0, 1001.0]\n",
    "\n",
    "print(sum(m > 1000 for m in montos))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "3\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso de dentro del `sum` es una comprensión, que vemos en el\n",
    "capítulo 4. Por ahora quédate con la idea: cada comparación devuelve True o\n",
    "False, y sumarlos cuenta los True."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. El error de tipos, provocado\n",
    "\n",
    "Provoca un `TypeError` sumando un texto y un\n",
    "número, y después arréglalo de dos formas distintas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "flete = '100'          # vino como texto del sistema\n",
    "descuento = 50\n",
    "\n",
    "print(int(flete) + descuento)      # los dos como numero\n",
    "print(flete + str(descuento))      # los dos como texto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "150\n",
    "10050\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las dos son válidas y hacen cosas muy distintas. Elegir mal aquí es de donde\n",
    "salen los reportes con números absurdos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. None no es cero\n",
    "\n",
    "Simula tres clientes donde uno no tiene fecha de última\n",
    "compra, y cuenta cuántos sí la tienen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fechas = ['2025-08-22', None, '2026-01-05']\n",
    "\n",
    "con_fecha = sum(f is not None for f in fechas)\n",
    "print(f'{con_fecha} de {len(fechas)} tienen fecha')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "2 de 3 tienen fecha\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este conteo es lo primero que haces al recibir cualquier archivo. En el\n",
    "capítulo 12 pandas te lo da en una línea, pero es exactamente la misma idea."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Cuáles de estas celdas están vacías de verdad\n",
    "\n",
    "Clasifica una fila de un Excel real, donde \"vacío\" viene de\n",
    "cinco formas distintas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "celdas = {'ciudad': 'Lima', 'descuento': 0.0, 'nota': '',\n",
    "          'telefono': '   ', 'satisfaccion': None, 'codigo': '0'}\n",
    "\n",
    "for nombre, valor in celdas.items():\n",
    "    print(f'{nombre:<13} {repr(valor):<8} '\n",
    "          f'vacio para Python: {not valor!s:<6} '\n",
    "          f'falta el dato: {valor is None}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ciudad        'Lima'   vacio para Python: False  falta el dato: False\n",
    "descuento     0.0      vacio para Python: True   falta el dato: False\n",
    "nota          ''       vacio para Python: True   falta el dato: False\n",
    "telefono      '   '    vacio para Python: False  falta el dato: False\n",
    "satisfaccion  None     vacio para Python: True   falta el dato: True\n",
    "codigo        '0'      vacio para Python: False  falta el dato: False\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en `telefono` y en `codigo`, que son los dos que\n",
    "mienten en direcciones opuestas 🎭\n",
    "\n",
    "El teléfono son tres espacios: para Python tiene contenido, para ti está\n",
    "vacío. El código es el texto `'0'`: para Python tiene contenido y para\n",
    "ti también, pero si lo conviertes a número se vuelve falso.\n",
    "\n",
    "Por eso, con texto que viene de un Excel, el `.strip()` va\n",
    "**antes** de preguntar si está vacío. Siempre."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. El promedio que se infla solo\n",
    "\n",
    "Calcula la satisfacción promedio filtrando de las dos\n",
    "maneras y mira la diferencia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "notas = [5, 0, 4, None, 0, 3, 5, None, 1]\n",
    "\n",
    "filtrado_mal = [n for n in notas if n]\n",
    "filtrado_bien = [n for n in notas if n is not None]\n",
    "\n",
    "print('respuestas contadas, mal :', len(filtrado_mal))\n",
    "print('respuestas contadas, bien:', len(filtrado_bien))\n",
    "print('promedio mal :', round(sum(filtrado_mal) / len(filtrado_mal), 2))\n",
    "print('promedio bien:', round(sum(filtrado_bien) / len(filtrado_bien), 2))\n",
    "print('inflado en   :',\n",
    "      round((sum(filtrado_mal) / len(filtrado_mal))\n",
    "            / (sum(filtrado_bien) / len(filtrado_bien)) * 100 - 100, 1), '%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "respuestas contadas, mal : 5\n",
    "respuestas contadas, bien: 7\n",
    "promedio mal : 3.6\n",
    "promedio bien: 2.57\n",
    "inflado en   : 40.0 %\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dos clientes pusieron cero, o sea la peor nota posible, y el primer filtro los\n",
    "borra del mapa 😤\n",
    "\n",
    "El resultado es una satisfacción promedio bastante mejor de la real, calculada\n",
    "sobre menos gente de la que contestó. Y todo por una letra de diferencia al\n",
    "escribir el filtro."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 13. Dos números iguales que no son el mismo\n",
    "\n",
    "Comprueba dónde `is` y `==` se separan."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pares = [('2500', 2500), ('250', 250), ('7', 7), ('100000', 100000)]\n",
    "\n",
    "for texto, numero in pares:\n",
    "    leido = int(texto)\n",
    "    print(f'{texto:>7}  ==  {leido == numero}   is  {leido is numero}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "   2500  ==  True   is  False\n",
    "    250  ==  True   is  True\n",
    "      7  ==  True   is  True\n",
    " 100000  ==  True   is  False\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los dos de en medio dicen `True` y los de los extremos\n",
    "`False`, y el corte está entre 256 y 257 🚧\n",
    "\n",
    "Ese es el rango que Python se guarda de antemano. Y lo que quiero que te lleves\n",
    "no es el número exacto: es que **un resultado que depende de si el número es\n",
    "grande o pequeño no puede ser la base de nada**. Con `==` las\n",
    "cuatro líneas darían `True`, que es lo que querías preguntar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 14. Redondear y cortar no son lo mismo\n",
    "\n",
    "Calcula el IGV de mil facturas y suma el total de tres\n",
    "maneras: exacta, redondeando cada factura, y cortando los decimales de cada\n",
    "factura."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "base = [round(3 + i * 0.7137, 3) for i in range(1000)]\n",
    "con_igv = [m * 1.18 for m in base]\n",
    "\n",
    "exacto = sum(con_igv)\n",
    "redondeado = sum(round(m, 2) for m in con_igv)\n",
    "truncado = sum(int(m * 100) / 100 for m in con_igv)\n",
    "\n",
    "print('exacto     : S/', round(exacto, 2))\n",
    "print('redondeando: S/', round(redondeado, 2),\n",
    "      ' se desvia', round(redondeado - exacto, 2))\n",
    "print('truncando  : S/', round(truncado, 2),\n",
    "      ' se desvia', round(truncado - exacto, 2))\n",
    "print('el truncado se come, por factura: S/',\n",
    "      round((exacto - truncado) / len(base), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "exacto     : S/ 424201.92\n",
    "redondeando: S/ 424201.91  se desvia -0.01\n",
    "truncando  : S/ 424196.99  se desvia -4.93\n",
    "el truncado se come, por factura: S/ 0.0049\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí la sorpresa, que va en contra de lo que yo esperaba antes de\n",
    "medirlo 🌸\n",
    "\n",
    "**Redondear en cada factura casi no desvía nada**: un céntimo en\n",
    "mil facturas. Porque la mitad de los redondeos suben y la otra mitad bajan, y se\n",
    "compensan solos.\n",
    "\n",
    "**Truncar sí desvía**, y bastante: casi cinco soles en las mismas\n",
    "mil facturas. `int(m * 100) / 100` corta los decimales, así que\n",
    "**siempre baja, nunca sube**. Medio céntimo por factura que va\n",
    "siempre en la misma dirección, y eso no se compensa con nada.\n",
    "\n",
    "La lección no es \"nunca redondees en medio\". Es esta: **lo que hace daño\n",
    "no es el error, es el sesgo**. Un error que se reparte para los dos lados\n",
    "se cancela. Uno que siempre va hacia el mismo lado se acumula, factura a factura,\n",
    "hasta que alguien pregunta por qué el reporte no cuadra 📞\n",
    "\n",
    "Aun así, la costumbre buena sigue siendo redondear solo al mostrar. El\n",
    "redondeo es una decisión de presentación, no de cálculo 🎁"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dos números iguales que Python dice que no lo son"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Antes de cerrar los tipos, una que parece un detalle de nada y se lleva tardes enteras. Léela despacio, porque el código de abajo no da error: da una respuesta 🧐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Comparas dos códigos de cliente con `is`, que se lee mejor que `==`. Lo pruebas con los primeros del archivo y funciona.\n",
    "\n",
    "```\n",
    "a = int('256')\n",
    "b = int('256')\n",
    "print(a is b)      # True\n",
    "\n",
    "a = int('257')\n",
    "b = int('257')\n",
    "print(a is b)      # False\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "Los dos son iguales y solo uno da True 😬 `is` no pregunta si valen lo mismo: pregunta si son **el mismo objeto en memoria**. Y Python guarda cacheados los enteros del -5 al 256, así que hasta ahí coinciden por casualidad.\n",
    "\n",
    "De 257 en adelante, dos números iguales son objetos distintos y `is` dice False. O sea que tu comparación funciona mientras pruebas con datos pequeños y empieza a fallar en producción, en silencio y solo con algunos clientes.\n",
    "\n",
    "La regla no tiene excepciones: **`==` para comparar valores, `is` solo con `None`**. `is None` está bien porque None es único de verdad."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Lees un CSV y al sumar dos columnas te sale un TypeError. ¿Qué pasó?\n",
    "\n",
    "a) Una de las dos es texto aunque parezca un número\n",
    "\n",
    "b) Hay un valor nulo en medio\n",
    "\n",
    "c) Los números son demasiado grandes\n",
    "\n",
    "d) Falta convertir a float\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Un nulo da otro problema distinto, y el mensaje sería otro.\n",
    "\n",
    "*c)* Python maneja enteros enormes sin quejarse.\n",
    "\n",
    "*d)* Vas bien encaminada, pero eso es la solución. La pregunta es qué pasó.\n",
    "\n",
    "Lo que dice el tipo es type(), no cómo se ve el valor."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🏷️ Una variable es una etiqueta pegada a un valor, y el `=` se lee\n",
    "de derecha a izquierda.\n",
    "\n",
    "- 🔍 Cuando algo raro pase, `type()` antes que nada.\n",
    "\n",
    "- 💸 Los decimales no son exactos: no los compares con `==` y\n",
    "redondea al final.\n",
    "\n",
    "- ✍️ Los métodos de texto devuelven una copia, no modifican la variable.\n",
    "\n",
    "- 🕳️ `None` es ausencia, no cero, y confundirlos te cambia los\n",
    "promedios.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El tipo no es un detalle del lenguaje. Es la diferencia entre sumar y\n",
    "pegar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si vienes de Excel esto ya lo sufriste: la celda que se alinea a la\n",
    "izquierda porque el número entró como texto. Es el mismo problema y está\n",
    "contado desde ahí en la [guía de\n",
    "Excel](https://missyera.com/guias/excel-desde-cero/) 📊\n",
    "\n",
    "En el capítulo 4 vamos a guardar muchas cosas juntas: listas, tuplas y\n",
    "diccionarios. Ahí aparece el error de copiar una lista que en realidad no se\n",
    "copió, que es de los más traicioneros que hay.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 3 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/variables-y-tipos/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
