{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Sacar datos de un texto escrito por personas\n",
    "\n",
    "Expresiones regulares sobre 2.440 comentarios de clientes, con sus tildes que faltan y sus emojis.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 13 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/texto-y-expresiones-regulares/\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": [
    "Doce capítulos limpiando números. Y en una empresa peruana lo que más llega no\n",
    "son números: son mensajes escritos por personas 💬\n",
    "\n",
    "Reclamos por WhatsApp, correos de clientes, comentarios de una encuesta. Ahí\n",
    "dentro está el número de pedido, el monto y el motivo, y hay que sacarlos.\n",
    "\n",
    "Antes de empezar, una pregunta: **¿cómo le explicarías a Python que\n",
    "busque \"un número de cuatro cifras que venga después de la palabra\n",
    "pedido\"?** Con lo del capítulo 3 no se puede. Con\n",
    "lo de este capítulo son doce caracteres 🔎"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Mira lo que llega de verdad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "import pandas as pd\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/comentarios-miss-yera.csv'\n",
    "c = pd.read_csv(URL)\n",
    "print('comentarios:', len(c))\n",
    "for texto in c['comentario'].head(4):\n",
    "    print(repr(texto))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en que uso `repr` y no `print` a secas, que es un\n",
    "truco del capítulo 7: así se ven los saltos de línea y\n",
    "los espacios del principio, que con `print` normal son invisibles y\n",
    "son justo la mitad del problema 👀"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('con espacios de sobra :', int((c['comentario'] != c['comentario'].str.strip()).sum()))\n",
    "print('con salto de linea    :', int(c['comentario'].str.contains('\\n').sum()))\n",
    "print('en MAYUSCULAS enteras :', int((c['comentario'].str.strip()\n",
    "                                      == c['comentario'].str.strip().str.upper()).sum()))\n",
    "print('con emoji             :', int(c['comentario'].str.contains(\n",
    "    r'[\\U0001F300-\\U0001FAFF]', regex=True).sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "311 con espacios de sobra y 718 con emoji. Y las 184 en mayúsculas no son un\n",
    "defecto: **son gente enojada**, o sea información 😤"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo mínimo que hay que saber"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una expresión regular es un patrón: una forma de describir \"un texto que se\n",
    "parece a esto\". Se escribe con símbolos y hay como veinte, de los cuales estos\n",
    "cinco resuelven casi todo:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Símbolo | Qué significa | Ejemplo |\n",
    "|---|---|---|\n",
    "| `\\d` | Un dígito | `\\d\\d\\d\\d` encuentra 4471 |\n",
    "| `\\w` | Una letra, dígito o guion bajo | `\\w+` encuentra una palabra |\n",
    "| `\\s` | Un espacio, tabulación o salto de línea | `\\s+` encuentra varios seguidos |\n",
    "| `+` | Uno o más de lo anterior | `\\d+` encuentra 12 y también 4471 |\n",
    "| `( )` | Guárdame esta parte | Lo veremos en un momento y es lo bueno |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "texto = 'ya van 12 dias y el pedido 4471 NO LLEGA, esto es el colmo'\n",
    "print(re.search(r'\\d+', texto))\n",
    "print(re.findall(r'\\d+', texto))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dos funciones y ya sabes la mitad. `re.search` devuelve\n",
    "**el primero** con su posición, y `re.findall` devuelve\n",
    "**todos** en una lista.\n",
    "\n",
    "Y ojo con la `r` de `r'\\d+'`, que no es decoración. Sin\n",
    "ella Python interpreta la barra invertida antes de que la expresión regular la\n",
    "vea, y el patrón deja de significar lo mismo. **Las expresiones regulares\n",
    "van siempre con `r` delante**, siempre, y así te ahorras un\n",
    "error que cuesta media tarde encontrar 🧯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los paréntesis, que es donde está el trabajo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Encontrar el número no basta: había dos y solo uno es el pedido. Los\n",
    "paréntesis dicen *qué parte me quiero llevar*:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = re.search(r'pedido\\s+(\\d{4})', texto)\n",
    "print('el trozo entero :', m.group(0))\n",
    "print('solo el numero  :', m.group(1))\n",
    "print('donde empieza   :', m.start())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se lee de izquierda a derecha, como una frase: *la palabra \"pedido\", uno o\n",
    "más espacios, y cuatro dígitos que me guardo* 📌\n",
    "\n",
    "El `{4}` dice exactamente cuántos, y es más seguro que\n",
    "`+`: con `+` también valdría un número de nueve cifras, que\n",
    "en estos datos sería otra cosa.\n",
    "\n",
    "Y `group(0)` es todo lo que coincidió, `group(1)` es el\n",
    "primer paréntesis. Ese desfase de uno confunde al principio y después se vuelve\n",
    "automático."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sobre las 2.440 filas a la vez"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "pandas trae las expresiones regulares metidas en las columnas de texto, así\n",
    "que no hace falta ningún bucle:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "c['pedido'] = c['comentario'].str.extract(r'pedido\\s+(\\d{4})')\n",
    "c['monto'] = c['comentario'].str.extract(r'S/\\s*([\\d.]+)')\n",
    "print('comentarios con numero de pedido:', int(c['pedido'].notna().sum()))\n",
    "print('comentarios con un monto        :', int(c['monto'].notna().sum()))\n",
    "for pedido, texto in c.loc[c['pedido'].notna(), ['pedido', 'comentario']].head(3).values:\n",
    "    print(f'  {pedido} <- {texto.strip()[:60]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De 2.440 mensajes sueltos salieron **265 números de pedido y 352\n",
    "montos**, en dos líneas 🎉\n",
    "\n",
    "Eso ya es una tabla que se puede cruzar con la base de pedidos del libro de\n",
    "[SQL desde cero](https://missyera.com/guias/sql-desde-cero/) y contestar cuántos reclamos\n",
    "tiene cada pedido 🗃️\n",
    "\n",
    "`str.extract` devuelve el primer paréntesis de cada fila, y\n",
    "`NaN` donde no encontró nada. Que devuelva nulo y no reviente es la\n",
    "decisión correcta: los otros 2.175 comentarios simplemente no traen pedido."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Anonimizar, que es lo que te van a pedir"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí viene el uso que más veces me ha tocado: **quitar los datos\n",
    "personales antes de que el texto salga a ningún lado**. Si vas a mandar\n",
    "estos comentarios a un modelo, a un proveedor o a un cuaderno compartido, esto no\n",
    "es opcional 🔒"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "CORREO = r'[\\w.+-]+@[\\w-]+\\.[\\w.]+'\n",
    "TELEFONO = r'\\b9\\d{8}\\b'\n",
    "c['anonimo'] = (c['comentario']\n",
    "                .str.replace(CORREO, '[correo]', regex=True)\n",
    "                .str.replace(TELEFONO, '[telefono]', regex=True))\n",
    "tocados = int((c['anonimo'] != c['comentario']).sum())\n",
    "print('comentarios anonimizados:', tocados)\n",
    "for antes, despues in zip(c.loc[c['anonimo'] != c['comentario'], 'comentario'].head(2),\n",
    "                          c.loc[c['anonimo'] != c['comentario'], 'anonimo'].head(2)):\n",
    "    print('  antes  :', antes.strip())\n",
    "    print('  despues:', despues.strip())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "288 comentarios tapados 🔒\n",
    "\n",
    "El `\\b` del teléfono es una **frontera de palabra**, y\n",
    "está ahí por una razón concreta: sin él, un número de trece cifras también\n",
    "coincidiría por dentro. `\\b` exige que ahí empiece y termine.\n",
    "\n",
    "Una advertencia honesta, porque esto es de las cosas donde equivocarse cuesta\n",
    "caro: **una expresión regular anonimiza lo que sabías buscar**. Si\n",
    "alguien escribió su teléfono con guiones o su DNI sin avisar, sigue ahí. Esto\n",
    "reduce el riesgo, no lo elimina, y hay que decirlo cuando entregas 🚩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Normalizar, o cuatro facturas que eran una"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El otro trabajo grande del texto es que la misma palabra viene escrita de\n",
    "varias formas. Es lo mismo que le pasaba a la ciudad en el capítulo\n",
    "12, y se arregla igual:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import unicodedata\n",
    "\n",
    "def normaliza(s):\n",
    "    s = s.strip().lower()\n",
    "    s = re.sub(r'\\s+', ' ', s)\n",
    "    s = unicodedata.normalize('NFKD', s)\n",
    "    s = s.encode('ascii', 'ignore').decode('utf-8')\n",
    "    return s\n",
    "\n",
    "c['limpio'] = c['comentario'].map(normaliza)\n",
    "print('antes :', repr(c['comentario'].iloc[3]))\n",
    "print('despues:', repr(c['limpio'].iloc[3]))\n",
    "print('formas distintas de \"factura\" antes  :',\n",
    "      c['comentario'].str.findall(r'(?i)factura').explode().dropna().nunique())\n",
    "print('formas distintas de \"factura\" despues:',\n",
    "      c['limpio'].str.findall(r'factura').explode().dropna().nunique())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuatro líneas y cada una arregla una cosa distinta 🧽\n",
    "\n",
    "- ✂️ `strip` quita los espacios de los extremos, los 311 de arriba.\n",
    "\n",
    "- 🔡 `lower` junta FACTURA con factura.\n",
    "\n",
    "- 🌬️ `re.sub(r'\\s+', ' ', s)` convierte cualquier racha de espacios\n",
    "o saltos de línea en un espacio solo. Es la línea que más veces vas a\n",
    "reutilizar.\n",
    "\n",
    "- 🇵🇪 Y las dos últimas quitan las tildes, para que \"atendió\" y \"atendio\" sean\n",
    "la misma palabra.\n",
    "\n",
    "Lo de las tildes es **una decisión y no una obviedad**: en un\n",
    "buscador ayuda, y si lo que vas a hacer es mostrarle el texto a una persona, se\n",
    "lo estás rompiendo. Guarda siempre el original al lado, que es para lo que existe\n",
    "la columna `limpio` aparte 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El símbolo que se come todo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora el error clásico, el que le pasa a todo el mundo la primera semana:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "codicioso = re.findall(r'\".+\"', 'el cliente dijo \"no llego\" y despues \"no responden\"')\n",
    "perezoso = re.findall(r'\".+?\"', 'el cliente dijo \"no llego\" y despues \"no responden\"')\n",
    "print('con .+   :', codicioso)\n",
    "print('con .+?  :', perezoso)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con `.+` sale **una sola cosa gigante** 😳\n",
    "\n",
    "Y no es un error de la librería: `.+` significa \"todo lo que\n",
    "puedas\", y lo que más puede es llegar hasta la última comilla. Se llama\n",
    "**codicioso**.\n",
    "\n",
    "La interrogación lo vuelve **perezoso**: \"todo lo que puedas, pero\n",
    "para en cuanto se pueda\". Esa interrogación de más es la diferencia entre dos\n",
    "resultados y uno."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una expresión regular hace lo que le pediste, no lo que querías. Y casi siempre pediste más de lo que creías."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error que sale cuando el patrón está mal escrito"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y este lo vas a ver el primer día, porque los paréntesis se olvidan solos:"
   ]
  },
  {
   "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",
    "    re.compile(r'(pedido \\d{4}')\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",
    "error: missing ), unterminated subpattern at position 0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*unterminated subpattern*, o sea que abriste un paréntesis y no lo\n",
    "cerraste. La `position 0` te dice en qué carácter del patrón está el\n",
    "problema, no de tu archivo 📍\n",
    "\n",
    "Es de los errores agradecidos: revienta enseguida y te dice dónde. El\n",
    "peligroso es el otro, el del patrón bien escrito que encuentra menos de lo que\n",
    "crees y no se queja 🚩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La negación, otra vez"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con el texto ya normalizado se puede contar de verdad. Y aquí sale lo mismo\n",
    "que en el libro de [deep learning desde\n",
    "cero](https://missyera.com/guias/deep-learning-desde-cero/), con otro disfraz:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(c[c['limpio'].str.contains(r'\\bno\\s+llego\\b')].shape[0], 'dicen que no llego')\n",
    "print(c[c['limpio'].str.contains(r'\\bllego\\b')].shape[0], 'dicen llego, contando los anteriores')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si cuentas la palabra \"llegó\" a secas te salen 383, y **55 de esos dicen\n",
    "justo lo contrario** 🙃\n",
    "\n",
    "Ese es el techo de contar palabras, y no se arregla con más expresiones\n",
    "regulares: se arregla mirando el orden, que es de lo que va el capítulo de\n",
    "recurrentes del otro libro. Aquí lo que hay que llevarse es *saber que el\n",
    "techo existe*."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuándo NO usar una expresión regular"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Si lo que tienes es | Usa |\n",
    "|---|---|\n",
    "| HTML de una página | Un lector de HTML de verdad. El HTML se anida y las expresiones regulares no saben de eso |\n",
    "| Un CSV o un JSON | `pandas` y `json`, que ya saben la estructura |\n",
    "| Buscar un texto fijo, sin patrón | `'palabra' in texto`, que es más rápido y se lee |\n",
    "| Validar un correo de verdad | Una librería. El patrón completo del estándar tiene cientos de caracteres |\n",
    "| Entender qué dice el mensaje | Nada de esto. Eso es procesamiento de lenguaje y empieza en el otro libro |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la regla que uso yo: **si el patrón no me cabe en un renglón, casi\n",
    "siempre es que la herramienta está mal elegida** 📏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Un equipo saca los montos de los reclamos para sumar cuánto se está reclamando en total. Corre sin errores y el número que sale se lleva a gerencia.\n",
    "\n",
    "```\n",
    "montos = c['comentario'].str.extract(r'S/\\s*([\\d.]+)')[0]\n",
    "total = pd.to_numeric(montos).sum()\n",
    "print(f'se reclaman S/ {total:,.2f} en total')\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "Dos cosas, y ninguna da error. La primera: `str.extract` se queda con **el primero** de cada comentario, así que un mensaje que mencione dos montos aporta uno solo y el total sale corto. La segunda y peor: el patrón `[\\d.]+` acepta cualquier mezcla de dígitos y puntos, así que en un texto donde el monto va al final de la frase se lleva también el punto final, y \"S/ 250.\" se convierte en un número que pandas puede leer distinto de lo que crees. Se arregla pidiendo la forma exacta, `S/\\s*(\\d+(?:\\.\\d{2})?)`, que es \"dígitos y opcionalmente un punto con exactamente dos decimales\". Y la comprobación que hay que hacer siempre antes de sumar nada: mira el máximo y el mínimo de lo que extrajiste, porque un total inflado no se nota y un máximo absurdo sí 🚩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seis, y el 4 es el que más se parece a un encargo de verdad. Intenta antes de\n",
    "abrir 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Los que gritan\n",
    "\n",
    "Cuenta cuántos comentarios están escritos enteros en\n",
    "mayúsculas y mira si están más en un tema que en otro."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "c['grita'] = c['comentario'].str.strip().str.isupper()\n",
    "print('gritan:', int(c['grita'].sum()))\n",
    "print(c.groupby('tema')['grita'].mean().round(4).to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Escribir en mayúsculas es una señal, no un defecto de datos. Si la proporción\n",
    "cambia mucho entre temas, ya tienes un indicador de urgencia sin ningún modelo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Contar los emojis\n",
    "\n",
    "Saca cuántos emojis tiene cada comentario y compara la media\n",
    "entre los molestos y los tranquilos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "c['emojis'] = c['comentario'].str.count(r'[\\U0001F300-\\U0001FAFF]')\n",
    "print(c.groupby('molesto')['emojis'].mean().round(3).to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El rango entre llaves es un trozo de la tabla Unicode. No hay que\n",
    "memorizarlo: hay que saber que los emojis son caracteres como cualquier otro y\n",
    "que se pueden buscar por rango."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Las horas que nadie te dijo que estaban\n",
    "\n",
    "Busca patrones tipo `14:30` en los comentarios.\n",
    "Escribe el patrón antes de correrlo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "horas = c['comentario'].str.extract(r'\\b(\\d{1,2}:\\d{2})\\b')\n",
    "print('comentarios con hora:', int(horas[0].notna().sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Puede que salgan cero, y eso también es un resultado: comprobar que algo NO\n",
    "está es tan útil como encontrarlo, y te ahorra escribir código para un caso que\n",
    "no existe."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El informe de una semana\n",
    "\n",
    "Junta todo el capítulo: normaliza, extrae el pedido,\n",
    "anonimiza, y saca una tabla de cuántos reclamos hay por tema y ciudad, solo de\n",
    "los molestos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "molestos = c[c['molesto'] == 1]\n",
    "tabla = molestos.pivot_table(index='ciudad', columns='tema',\n",
    "                             values='id_comentario', aggfunc='count')\n",
    "print(tabla.fillna(0).astype(int).to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto es lo que se entrega, y fíjate en que el código de expresiones regulares\n",
    "es la parte corta. Lo largo es decidir qué mirar, igual que en el capítulo\n",
    "16."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Rómpelo a propósito\n",
    "\n",
    "Quita la `r` de delante de un patrón con\n",
    "`\\d` y mira qué pasa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(re.findall('\\\\d+', 'el pedido 4471'))   # con r delante, funciona\n",
    "print(re.findall('\\d+', 'el pedido 4471'))     # sin r, Python avisa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En las versiones recientes Python lanza un aviso porque `\\d` no es\n",
    "una secuencia de escape válida de una cadena normal. Hoy avisa, y en algún\n",
    "momento va a ser un error. Por eso la `r` va siempre."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Tu propio buzón\n",
    "\n",
    "Sin dataset. Exporta veinte mensajes de un WhatsApp de\n",
    "trabajo tuyo y pásales `normaliza` y el anonimizador."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es el ejercicio que más enseña de los seis, porque tus datos van a tener una\n",
    "suciedad que este archivo no tiene y vas a descubrir sola qué patrón te falta.\n",
    "Ojo con una cosa: si son mensajes de clientes de verdad, anonimízalos\n",
    "**antes** de pegarlos en ningún sitio 🔒"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Te piden sacar el número de guía de remisión de 3.000 correos. Vienen como \"GR-0012345\", pero algunos escriben \"GR 0012345\" y otros \"gr0012345\". ¿Qué patrón escribes?\n",
    "\n",
    "a) `(?i)gr[-\\s]?(\\d{7})`\n",
    "\n",
    "b) `GR-(\\d{7})`\n",
    "\n",
    "c) `(?i)gr.*(\\d+)`\n",
    "\n",
    "d) Un bucle con `if` para cada forma\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Ese solo encuentra la primera forma. Te vas a perder los otros dos casos y no te vas a enterar, porque no da error.\n",
    "\n",
    "*c)* El punto con asterisco es codicioso y se va a comer hasta el último número del correo. Y el más de dígitos acepta cualquier cantidad de cifras, no siete.\n",
    "\n",
    "*d)* Funciona y no escala: en cuanto aparezca una cuarta forma hay que tocar el código otra vez.\n",
    "\n",
    "Y antes de darlo por bueno, cuenta cuántos encontró de los 3.000. Si son muchos menos de los que esperabas, falta una forma de escribirlo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🔎 `\\d`, `\\w`, `\\s`, `+` y los\n",
    "paréntesis resuelven casi todo.\n",
    "\n",
    "- 🅁 La `r` va siempre delante del patrón.\n",
    "\n",
    "- 📌 Los paréntesis dicen qué parte te llevas, y `str.extract` lo\n",
    "hace sobre la columna entera.\n",
    "\n",
    "- 🔒 Anonimizar es dos `str.replace`, y solo tapa lo que supiste\n",
    "buscar.\n",
    "\n",
    "- 🍽️ `.+` se come todo. `.+?` para en cuanto puede.\n",
    "\n",
    "- 🚫 Y si el patrón no cabe en un renglón, revisa si la herramienta es la\n",
    "correcta.\n",
    "\n",
    "Con esto ya puedes limpiar lo que llega en texto. Lo que viene después,\n",
    "entender qué *dice*, es otro oficio y empieza en el\n",
    "[libro de deep learning desde\n",
    "cero](https://missyera.com/guias/deep-learning-desde-cero/) 🧠\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 13 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/texto-y-expresiones-regulares/\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
}
