string_one = "This is a string"
string_two = (
"If I want to include a 'quote' inside a string, I use double quotes on the outside"
)Strings y texto
Introducción
Los strings (cadenas de texto) son simplemente el tipo de dato para el texto. Hasta ahora has usado strings, pero sin aprender mucho sobre sus detalles. Es momento de profundizar en ellos, aprender cómo funcionan y dominar algunas de las potentes herramientas de manipulación de strings que tienes a tu disposición.
Este capítulo se ha beneficiado del Python String Cook Book y del Python Data Science Handbook de Jake VanderPlas.
Ten en cuenta que existen métodos más potentes para trabajar con strings llamados expresiones regulares, pero se tratarán en otro capítulo.
Crear strings
Ya hemos creado strings de pasada en capítulos anteriores, pero sin entrar en detalles. En primer lugar, puedes crear un string usando comillas simples (') o dobles ("). Conviene ser consistente, aunque no importe cuál uses, pero los formateadores automáticos de código suelen preferir ". Si tienes una comilla dentro de un string, usa ' dentro de él.
Los strings son de tipo str:
type(string_one)str
Los strings en Python se pueden indexar, así que podemos extraer ciertos caracteres usando corchetes para indicar qué posiciones queremos.
var = "banana"var[:3]'ban'
Los trucos habituales de slicing (rebanado) que se aplican a las listas también funcionan con strings, es decir, puedes obtener las posiciones que quieras usando la sintaxis var[start:stop:step]. Aquí tienes un ejemplo que obtiene un carácter sí y otro no del string, empezando por la 2.ª posición.
var[1::2]'aaa'
Ten en cuenta que los strings, al igual que las tuplas como (1, 2, 3) pero a diferencia de las listas como [1, 2, 3], son inmutables. Esto significa que comandos como var[0] = "B" producirán un error. Si quieres cambiar un solo carácter, tendrás que reemplazar el string completo. En este ejemplo, el comando para hacerlo sería var = "Banana".
Al igual que con las listas, puedes obtener la longitud de un string con len():
len(var)6
Puedes concatenar strings con el operador +:
string_one + ". " + string_two + ".""This is a string. If I want to include a 'quote' inside a string, I use double quotes on the outside."
Fíjate en que añadimos caracteres extra para que la frase tuviera sentido. Otra forma de lograr lo mismo, que escala de manera más eficiente a muchas palabras o frases (si las tienes en una lista), es:
". ".join([string_one, string_two])"This is a string. If I want to include a 'quote' inside a string, I use double quotes on the outside"
Tres funciones útiles que conviene conocer son upper(), lower() y title(). Veamos qué hacen
var = "input TEXT"
var_list = [var.upper(), var.lower(), var.title()]
print(var_list)['INPUT TEXT', 'input text', 'Input Text']
Ten en cuenta que hay muchas funciones integradas para trabajar con strings en Python; puedes encontrar una lista completa aquí.
Invierte el string "gnirts desrever a si sihT" usando operaciones de indexación.
Ya que estamos usando print(), tiene algunos trucos. Si tenemos una lista, podemos imprimir sus elementos con un separador dado:
print(*var_list, sep="; and \n")INPUT TEXT; and
input text; and
Input Text
(Pronto veremos qué hace ‘’.) Para convertir variables de otros tipos en strings, usa la función str(), por ejemplo
(
"A boolean is either "
+ str(True)
+ " or "
+ str(False)
+ ", there are only "
+ str(2)
+ " options."
)'A boolean is either True or False, there are only 2 options.'
En este ejemplo, dos variables boolean y una variable entera se convirtieron en strings. str() generalmente deduce de forma inteligente cómo quieres convertir tu variable de tipo no string en un string. Puedes pasarle a str() una variable o un valor literal.
f-strings
El ejemplo anterior es bastante verboso. Otra forma de combinar strings con variables es mediante f-strings. Un f-string sencillo tiene este aspecto:
variable = 15.32399
print(f"You scored {variable}")You scored 15.32399
Esto es similar a llamar a str sobre la variable y usar + para concatenar, pero mucho más corto de escribir. También puedes añadir expresiones a los f-strings:
print(f"You scored {variable**2}")You scored 234.8246695201
Esto también funciona con funciones; al fin y al cabo, **2 es solo una función con su propia sintaxis especial.
En este ejemplo, el número de la puntuación que obtuvimos tenía muchos decimales (probablemente) poco interesantes. ¿Cómo pulimos la salida impresa? Puedes pasar más información al f-string para obtener la salida con el formato exacto que quieras. Supongamos que queremos dos decimales y un signo (aunque siempre escribes + en el formato, el signo sale como + o - según el valor):
print(f"You scored {variable:+.2f}")You scored +15.32
Existe toda una gama de opciones de formato para números, como se muestra en la siguiente tabla:
| Número | Formato | Salida | Descripción |
|---|---|---|---|
| 15.32347 | {:.2f} | 15.32 | Float con 2 decimales |
| 15.32347 | {:+.2f} | +15.32 | Float con 2 decimales y signo |
| -1 | {:+.2f} | -1.00 | Float con 2 decimales y signo |
| 15.32347 | {:.0f} | 15 | Float sin decimales |
| 3 | {:0>2d} | 03 | Rellenar número con ceros (relleno a la izquierda, ancho 2) |
| 3 | {:*<4d} | 3*** | Rellenar número con * (relleno a la derecha, ancho 4) |
| 13 | {:*<4d} | 13** | Rellenar número con * (relleno a la derecha, ancho 4) |
| 1000000 | {:,} | 1,000,000 | Número con coma como separador |
| 0.25 | {:.1%} | 25.0% | Formato de porcentaje |
| 1000000000 | {:.2e} | 1.00e+09 | Notación exponencial |
| 12 | {:10d} | 12 | Alineado a la derecha (por defecto, ancho 10) |
| 12 | {:<10d} | 12 | Alineado a la izquierda (ancho 10) |
| 12 | {:^10d} | 12 | Centrado (ancho 10) |
Además de usar esta página de forma interactiva mediante los enlaces de Colab y Binder en la parte superior, o de descargarla y usarla en tu propio ordenador, puedes experimentar con algunas de estas opciones en este enlace.
Caracteres especiales y cómo escapar strings
Python tiene un módulo string que incluye algunos strings y caracteres integrados útiles. Por ejemplo
import string
string.punctuation'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
te da todos los signos de puntuación,
string.ascii_letters'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
devuelve todas las letras básicas de la codificación ‘ASCII’ (con las variantes .ascii_lowercase y .ascii_uppercase), y
string.digits'0123456789'
te da los números del 0 al 9. Por último, aunque menos impresionante visualmente, string.whitespace da un string que contiene todos los distintos tipos (¡hay más de uno!) de espacio en blanco.
Hay otros caracteres especiales; de hecho, ya conocimos el más famoso de ellos: “” para nueva línea. Para imprimir realmente “” tenemos que ‘escapar’ la barra invertida añadiendo otra barra invertida:
print("Here is a \n new line")
print("Here is an \\n escaped new line ")Here is a
new line
Here is an \n escaped new line
La siguiente tabla muestra los comandos de escape más importantes:
| Código | Resultado |
|---|---|
\' |
Comilla simple (útil si usas ' para los strings) |
\" |
Comilla doble (útil si usas " para los strings) |
\\ |
Barra invertida |
\n |
Nueva línea |
\r |
Retorno de carro |
\t |
Tabulación |
Aquí tienes un ejemplo más complicado:
print("a\tb\nA\tB")a b
A B
Raw strings
Los strings con el prefijo r, como r’…’ y r”…“, se llaman raw strings (strings sin procesar) y tratan las barras invertidas como caracteres literales en lugar de caracteres especiales.
print(r"a\tb\nA\tB")a\tb\nA\tB
Limpiar texto
A menudo querrás hacer cambios en el texto con el que trabajas. En esta sección veremos las distintas opciones para hacerlo.
Reemplazar substrings
Una tarea habitual con texto es reemplazar un substring dentro de un string más largo. Supongamos que tienes una variable de tipo string var. Puedes usar .replace(old_text, new_text) para hacerlo.
"Value is objective".replace("objective", "subjective")'Value is subjective'
Como con cualquier variable de un tipo específico (aquí, string), esto también funcionaría con variables:
text = "Value is objective"
old_substr = "objective"
new_substr = "subjective"
text.replace(old_substr, new_substr)'Value is subjective'
Ten en cuenta que .replace() realiza un reemplazo exacto y, por tanto, distingue entre mayúsculas y minúsculas.
Reemplazar caracteres con translate
Un carácter es un elemento individual dentro de un string, como la ‘l’ en ‘equilibrium’. Siempre puedes contar el número de caracteres de una variable string llamada var usando len(var). Un método muy rápido para reemplazar caracteres individuales en un string es str.translate().
Reemplazar caracteres es extremadamente útil en ciertas situaciones, sobre todo cuando quieres eliminar toda la puntuación antes de hacer otros análisis de texto. Para ello puedes usar el string integrado string.punctuation.
Veamos cómo usarlo para eliminar todas las vocales de un texto. Con disculpas a la economista Lisa Cook, usaremos el resumen de Cook (2011) como el texto que modificaremos, y primero crearemos un diccionario de traducciones de vocales a nada, es decir, "".
example_text = "Much recent work has focused on the influence of social capital on innovative outcomes. Little research has been done on disadvantaged groups who were often restricted from participation in social networks that provide information necessary for invention and innovation. Unique new data on African American inventors and patentees between 1843 and 1930 permit an empirical investigation of the relation between social capital and economic outcomes. I find that African Americans used both traditional, i.e., occupation-based, and nontraditional, i.e., civic, networks to maximize inventive output and that laws constraining social-capital formation are most negatively correlated with economically important inventive activity."
vowels = "aeiou"
translation_dict = {x: "" for x in vowels}
translation_dict{'a': '', 'e': '', 'i': '', 'o': '', 'u': ''}
Ahora convertimos nuestro diccionario en un traductor de strings y lo aplicamos a nuestro texto:
translator = example_text.maketrans(translation_dict)
example_text.translate(translator)'Mch rcnt wrk hs fcsd n th nflnc f scl cptl n nnvtv tcms. Lttl rsrch hs bn dn n dsdvntgd grps wh wr ftn rstrctd frm prtcptn n scl ntwrks tht prvd nfrmtn ncssry fr nvntn nd nnvtn. Unq nw dt n Afrcn Amrcn nvntrs nd ptnts btwn 1843 nd 1930 prmt n mprcl nvstgtn f th rltn btwn scl cptl nd cnmc tcms. I fnd tht Afrcn Amrcns sd bth trdtnl, .., ccptn-bsd, nd nntrdtnl, .., cvc, ntwrks t mxmz nvntv tpt nd tht lws cnstrnng scl-cptl frmtn r mst ngtvly crrltd wth cnmclly mprtnt nvntv ctvty.'
Usa translate para reemplazar con espacios todos los signos de puntuación de la siguiente oración: “The well-known story I told at the conferences [about hypocondria] in Boston, New York, Philadelphia,…and Richmond went as follows: It amused people who knew Tommy to hear this; however, it distressed Suzi when Tommy (1982–2019) asked, "How can I find out who yelled, ‘Fire!’ in the theater?" and then didn’t wait to hear Missy give the answer—‘Dick Tracy.’”
En general, str.translate es muy rápido para reemplazar caracteres individuales en strings. Pero también puedes hacerlo con una list comprehension y un join() de la lista resultante, así:
"".join(
[
ch
for ch in "Example. string. with- excess_ [punctuation]/,"
if ch not in string.punctuation
]
)'Example string with excess punctuation'
Dividir strings
Si quieres dividir un string en una posición concreta, hay dos formas rápidas de hacerlo. La primera es usar métodos de indexación, que funcionan bien si sabes en qué posición quieres dividir el texto, p. ej.
"This is a sentence and we will split it at character 18"[:18]'This is a sentence'
A continuación, podemos usar la función integrada split(), que devuelve una lista con las partes separadas en los lugares donde aparece un substring dado:
"This is a sentence. And another sentence. And a third sentence".split(".")['This is a sentence', ' And another sentence', ' And a third sentence']
Ten en cuenta que el carácter usado para dividir el string se elimina de la lista de strings resultante. Veamos un ejemplo usando un string para dividir en lugar de un solo carácter:
"This is a sentence. And another sentence. And a third sentence".split("sentence")['This is a ', '. And another ', '. And a third ', '']
Otra función útil que conviene conocer es splitlines(), que divide un string en los saltos de línea y devuelve las partes como una lista.
count y find
Hagamos un conteo sencillo de palabras dentro de un texto usando str.count(). Usaremos como texto la primera estrofa de la sextina ‘A Miracle for Breakfast’ de Elizabeth Bishop.
text = "At six o'clock we were waiting for coffee, \n waiting for coffee and the charitable crumb \n that was going to be served from a certain balcony \n --like kings of old, or like a miracle. \n It was still dark. One foot of the sun \n steadied itself on a long ripple in the river."
word = "coffee"
print(f'The word "{word}" appears {text.count(word)} times.')The word "coffee" appears 2 times.
Por su parte, find() devuelve la posición en la que aparece una palabra o un carácter concreto.
text.find(word)35
Podemos comprobarlo usando el número que obtenemos y un poco de indexación de strings:
text[text.find(word) : text.find(word) + len(word)]'coffee'
Pero este no es el único lugar donde aparece la palabra ‘coffee’. Si queremos encontrar la última aparición, se hace así:
text.rfind(word)57
Trabajar con múltiples strings
Hemos visto cómo trabajar con strings individuales. Pero a menudo queremos trabajar con un grupo de strings, lo que también se conoce como corpus, es decir, una colección de textos. Puede ser una colección de palabras, oraciones, párrafos o alguna agrupación basada en un dominio (por ejemplo, descripciones de puestos de trabajo). Como cualquier otro objeto de Python, puedes poner strings en una lista (u otro iterable).
Y, por suerte, muchos de los métodos que hemos visto aplicados a un único string pueden escalarse directamente a cientos, miles o millones de strings usando pandas u otras herramientas. Este escalado se logra mediante la vectorización, por analogía con pasar de un único valor (un escalar) a múltiples valores en una lista (un vector).
Como ejemplo muy mínimo, aquí tienes la conversión a mayúscula inicial de nombres vectorizada mediante una comprensión de listas:
[name.capitalize() for name in ["ada", "adam", "elinor", "grace", "jean"]]['Ada', 'Adam', 'Elinor', 'Grace', 'Jean']
Se puede usar una serie de pandas en lugar de una lista. Primero creemos la serie:
import pandas as pd
dfs = pd.Series(
["ada lovelace", "adam smith", "elinor ostrom", "grace hopper", "jean bartik"],
dtype="string",
)
dfs0 ada lovelace
1 adam smith
2 elinor ostrom
3 grace hopper
4 jean bartik
dtype: string
Ahora usamos la sintaxis series.str.function para modificar la serie de texto:
dfs.str.title()0 Ada Lovelace
1 Adam Smith
2 Elinor Ostrom
3 Grace Hopper
4 Jean Bartik
dtype: string
Si tuviéramos un dataframe y no una serie, la sintaxis cambiaría para referirse solo a la columna de interés, así:
df = pd.DataFrame(dfs, columns=["names"])
df["names"].str.title()0 Ada Lovelace
1 Adam Smith
2 Elinor Ostrom
3 Grace Hopper
4 Jean Bartik
Name: names, dtype: string
La siguiente tabla muestra una lista no exhaustiva de los métodos de strings disponibles en pandas.
Función (precedida de .str.) |
Qué hace |
|---|---|
len() |
Longitud del string. |
lower() |
Pone el string en minúsculas. |
upper() |
Pone el string en mayúsculas. |
capitalize() |
Pone en mayúscula la primera letra del string. |
swapcase() |
Intercambia mayúsculas y minúsculas en un string. |
translate() |
Devuelve una copia del string en la que cada carácter se ha transformado mediante una tabla de traducción dada. |
ljust() |
Rellena un string por la izquierda (por defecto, con espacios) |
rjust() |
Rellena un string por la derecha (por defecto, con espacios) |
center() |
Rellena de modo que el string quede centrado (por defecto, con espacios) |
zfill() |
Rellena con ceros |
strip() |
Elimina los espacios en blanco iniciales y finales |
rstrip() |
Elimina los espacios en blanco finales |
lstrip() |
Elimina los espacios en blanco iniciales |
find() |
Devuelve el índice más bajo en los datos donde aparece un substring |
split() |
Divide el string usando como delimitador el substring indicado |
isupper() |
Comprueba si el string está en mayúsculas |
isdigit() |
Comprueba si el string está compuesto por dígitos |
islower() |
Comprueba si el string está en minúsculas |
startswith() |
Comprueba si el string empieza por un substring dado |
Las expresiones regulares también se pueden escalar con pandas. La siguiente tabla muestra expresiones regulares vectorizadas.
| Función | Qué hace |
|---|---|
match() |
Llama a re.match() sobre cada elemento y devuelve un boolean. |
extract() |
Llama a re.match() sobre cada elemento y devuelve los grupos coincidentes como strings. |
findall() |
Llama a re.findall() sobre cada elemento |
replace() |
Reemplaza las apariciones del patrón por otro string |
contains() |
Llama a re.search() sobre cada elemento y devuelve un boolean |
count() |
Cuenta las apariciones del patrón |
split() |
Equivalente a str.split(), pero acepta regex |
rsplit() |
Equivalente a str.rsplit(), pero acepta regex |
Veamos un par de ellas en acción. Primero, dividir según un substring dado:
df["names"].str.split(" ")0 [ada, lovelace]
1 [adam, smith]
2 [elinor, ostrom]
3 [grace, hopper]
4 [jean, bartik]
Name: names, dtype: object
Es bastante habitual querer dividir strings y guardar los resultados en nuevas columnas del dataframe. Puedes especificar un número (máximo) de divisiones mediante el kwarg n= y obtener las columnas usando expand
df["names"].str.split(" ", n=2, expand=True)| 0 | 1 | |
|---|---|---|
| 0 | ada | lovelace |
| 1 | adam | smith |
| 2 | elinor | ostrom |
| 3 | grace | hopper |
| 4 | jean | bartik |
Usando operaciones vectorizadas, crea una nueva columna con la posición del índice donde aparece la primera vocal en cada fila de la columna names.
Aquí tienes un ejemplo de uso de una función regex con pandas:
df["names"].str.extract("(\w+)", expand=False)<>:1: SyntaxWarning: invalid escape sequence '\w'
<>:1: SyntaxWarning: invalid escape sequence '\w'
/var/folders/8n/n8x6tb9n5tz72hhnwdf7l04w0000gn/T/ipykernel_75795/3354940721.py:1: SyntaxWarning: invalid escape sequence '\w'
df["names"].str.extract("(\w+)", expand=False)
0 ada
1 adam
2 elinor
3 grace
4 jean
Name: names, dtype: string
Hay algunas operaciones vectorizadas más sobre strings que resultan útiles.
| Método | Descripción |
|---|---|
get() |
Indexa cada elemento |
slice() |
Extrae una porción de cada elemento |
slice_replace() |
Reemplaza una porción de cada elemento por el valor indicado |
cat() |
Concatena strings |
repeat() |
Repite valores |
normalize() |
Devuelve la forma Unicode del string |
pad() |
Añade espacios en blanco a la izquierda, a la derecha o a ambos lados de los strings |
wrap() |
Divide strings largos en líneas de longitud menor que un ancho dado |
join() |
Une los strings de cada elemento de la Series con el separador indicado |
get_dummies() |
Extrae variables dummy como un dataframe |
Los métodos get() y slice() dan acceso a los elementos de las listas devueltas por split(). Aquí tienes un ejemplo que combina split() y get():
df["names"].str.split().str.get(-1)0 lovelace
1 smith
2 ostrom
3 hopper
4 bartik
Name: names, dtype: object
Si tenemos una columna con etiquetas separadas por un símbolo, podemos usar la función get_dummies() para separarlas. Por ejemplo, creemos un dataframe con una sola columna que mezcla etiquetas de materia y de nacionalidad:
df = pd.DataFrame(
{
"names": [
"ada lovelace",
"adam smith",
"elinor ostrom",
"grace hopper",
"jean bartik",
],
"tags": ["uk; cs", "uk; econ", "usa; econ", "usa; cs", "usa; cs"],
}
)
df| names | tags | |
|---|---|---|
| 0 | ada lovelace | uk; cs |
| 1 | adam smith | uk; econ |
| 2 | elinor ostrom | usa; econ |
| 3 | grace hopper | usa; cs |
| 4 | jean bartik | usa; cs |
Si ahora usamos str.get_dummies() y dividimos por ;, obtenemos un dataframe de variables dummy.
df["tags"].str.get_dummies(";")| cs | econ | uk | usa | |
|---|---|---|---|---|
| 0 | 1 | 0 | 1 | 0 |
| 1 | 0 | 1 | 1 | 0 |
| 2 | 0 | 1 | 0 | 1 |
| 3 | 1 | 0 | 0 | 1 |
| 4 | 1 | 0 | 0 | 1 |
Leer texto
Archivo de texto
Si tienes simplemente un archivo de texto plano, puedes leerlo así:
fname = 'book.txt'
with open(fname, encoding='utf-8') as f:
text_of_book = f.read()También puedes leer un archivo de texto directamente en un dataframe de pandas usando
df = pd.read_csv('book.txt', delimiter = "\n")En el ejemplo anterior, el delimitador entre las distintas filas del dataframe se establece como “”, que significa nueva línea, pero puedes usar el delimitador que prefieras.
Descarga el archivo ‘smith_won.txt’ usando este enlace (haz clic derecho y elige “guardar como”). Luego lee el texto con pandas.
Archivo CSV
Los archivos CSV ya están divididos en filas. La forma más sencilla, con diferencia, de leer archivos csv es usar pandas,
df = pd.read_csv('book.csv')Recuerda que pandas también puede leer muchos otros tipos de archivo.