import pandas as pdImportación de datos
Introducción
En este capítulo vamos a empezar a trabajar con tus propios datos. Aprenderás a leer en Python archivos rectangulares de texto plano. Solo veremos lo más básico de la importación de datos, pero muchos de los principios se aplican a otras formas de datos. Terminaremos con algunas indicaciones para abrir otros tipos de datos.
Requisitos previos
Necesitarás tener instalado el paquete pandas. Debes asegurarte de tener pandas instalado. Para comprobarlo, e importar pandas en tu sesión, ejecuta
Si este comando falla, no tienes pandas instalado. Abre la terminal en Visual Studio Code (Terminal -> New Terminal), haz cd a la carpeta en la que estás trabajando y escribe uv add pandas. Ten en cuenta que, una vez instalado pandas, la convención es importarlo en tu sesión de Python con el nombre pd poniendo import pandas as pd al principio de tu script.
Primeros pasos
pandas admite una enorme variedad de formatos de entrada y salida: Stata (.dta), Excel (.xls, .xlsx), csv, tsv, formatos de big data (HDF5, parquet), JSON, SAS, SPSS, SQL y más; hay una lista completa de los formatos disponibles en la documentación.
Aunque pandas tiene muchísimas formas de leer datos y cargarlos en tu sesión de Python, aquí nos centraremos en el humilde archivo de tabla en texto plano; por ejemplo, csv (valores separados por comas) y tsv (valores separados por tabulaciones).
Leer datos de un archivo
Todo lo necesario para abrir archivos de tabla en texto plano está contenido en una sola función, pd.read_csv(). Admite numerosos argumentos, pero los dos más importantes son el primero (sin nombre), que indica la ruta a los datos, y sep= (un argumento con nombre), que le dice a pandas si los valores están separados por comas, tabulaciones u otro carácter; sin embargo, si dejas este campo vacío, pandas lo adivinará por ti. Para ver el conjunto completo de argumentos, ejecuta help(pd.read_csv).
Así se ve un archivo CSV sencillo con una fila de nombres de columna (también llamada habitualmente fila de encabezado) y seis filas de datos (usando la terminal):
! cat data/students.csvStudent ID,Full Name,favourite.food,mealPlan,AGE
1,Sunil Huffmann,Strawberry yoghurt,Lunch only,4
2,Barclay Lynn,French fries,Lunch only,5
3,Jayendra Lyne,N/A,Breakfast and lunch,7
4,Leon Rossini,Anchovies,Lunch only,8
5,Chidiegwu Dunkel,Pizza,Breakfast and lunch,five
6,Güvenç Attila,Ice cream,Lunch only,6
Fíjate en que es un archivo CSV, así que los valores están separados por comas. Ahora carguémoslo en un dataframe de pandas en Python:
students = pd.read_csv("data/students.csv")
students| Student ID | Full Name | favourite.food | mealPlan | AGE | |
|---|---|---|---|---|---|
| 0 | 1 | Sunil Huffmann | Strawberry yoghurt | Lunch only | 4 |
| 1 | 2 | Barclay Lynn | French fries | Lunch only | 5 |
| 2 | 3 | Jayendra Lyne | NaN | Breakfast and lunch | 7 |
| 3 | 4 | Leon Rossini | Anchovies | Lunch only | 8 |
| 4 | 5 | Chidiegwu Dunkel | Pizza | Breakfast and lunch | five |
| 5 | 6 | Güvenç Attila | Ice cream | Lunch only | 6 |
Si quieres descargar estos datos para probarlo por tu cuenta, ve a este enlace, haz clic derecho en ‘Raw’ y selecciona “Guardar enlace como…”. Guarda los datos en un directorio llamado ‘data’ con el nombre ‘students.csv’. Este directorio debe estar dentro de tu carpeta activa de Visual Studio Code. Puedes comprobar en qué carpeta estás ejecutando el siguiente código:
import os
os.getcwd() # get current working directory (cwd)Si devuelve ‘python4DS’, tus datos descargados deberían estar en ‘python4DS/data/students.csv’.
El primer argumento de read_csv() fue la ruta a los datos, y pandas adivinó que este archivo usa comas como separador.
La función de lectura de CSV crea automáticamente un nuevo índice (que es simplemente la posición de cada fila) y toma la primera línea de datos como encabezado o nombres de columna. Pero quizá quieras ajustar este comportamiento de varias formas.
A veces hay algunas líneas de metadatos al principio del archivo. Puedes usar
skiprows=npara omitir las primerasnlíneas, por ejemplopd.read_csv("data/students.csv", skiprows=2).Puede que los datos no tengan nombres de columna. Puedes usar
names =con una lista para indicarle aread_csv()que use otra opción para los nombres de columna. Por ejemplo,pd.read_csv("data/students.csv", names=range(5))pondría los números del 0 al 4 como nombres de columna.Quizá quieras cambiar qué columna se usa como índice. El comportamiento por defecto es crear un índice, pero en estos datos vemos que ya hay una columna de ID que podríamos usar. Para ello, usa el argumento
index_col=, por ejemplopd.read_csv("data/students.csv", index_col=0).
Esto es todo lo que necesitas saber para leer ~75% de los archivos CSV que encontrarás en la práctica. Los archivos separados por tabulaciones y los de ancho fijo se leen con la misma función.
Primeros pasos
Echemos otro vistazo a los datos de students.
Una vez que lees los datos, el primer paso suele consistir en transformarlos de alguna manera para que sea más fácil trabajar con ellos en el resto del análisis. Por ejemplo, los nombres de columna del archivo students que leímos tienen formatos poco estándar.
Podrías renombrarlas una por una con .rename() o usar una función práctica de otro paquete para limpiarlas y convertirlas todas a snake case de una vez. Usaremos el paquete skimpy para ello. Instálalo ejecutando uv add skimpy en la terminal.
De skimpy usaremos la función clean_columns(); recibe un dataframe y devuelve un dataframe con los nombres de las variables convertidos a snake case.
from skimpy import clean_columns
students = clean_columns(students)
students| student_id | full_name | favourite_food | meal_plan | age | |
|---|---|---|---|---|---|
| 0 | 1 | Sunil Huffmann | Strawberry yoghurt | Lunch only | 4 |
| 1 | 2 | Barclay Lynn | French fries | Lunch only | 5 |
| 2 | 3 | Jayendra Lyne | NaN | Breakfast and lunch | 7 |
| 3 | 4 | Leon Rossini | Anchovies | Lunch only | 8 |
| 4 | 5 | Chidiegwu Dunkel | Pizza | Breakfast and lunch | five |
| 5 | 6 | Güvenç Attila | Ice cream | Lunch only | 6 |
Otra tarea habitual después de leer los datos es revisar los tipos de las variables. En la columna favourite_food hay varios alimentos y luego el valor NaN, que se ha leído como un número de punto flotante en lugar de un string faltante. Podemos solucionarlo convirtiendo explícitamente esa columna a strings:
students["favourite_food"] = students["favourite_food"].astype("string")
students| student_id | full_name | favourite_food | meal_plan | age | |
|---|---|---|---|---|---|
| 0 | 1 | Sunil Huffmann | Strawberry yoghurt | Lunch only | 4 |
| 1 | 2 | Barclay Lynn | French fries | Lunch only | 5 |
| 2 | 3 | Jayendra Lyne | <NA> | Breakfast and lunch | 7 |
| 3 | 4 | Leon Rossini | Anchovies | Lunch only | 8 |
| 4 | 5 | Chidiegwu Dunkel | Pizza | Breakfast and lunch | five |
| 5 | 6 | Güvenç Attila | Ice cream | Lunch only | 6 |
De forma similar, "age" tiene tipos de datos mezclados: ¡string y entero! Convirtamos ‘five’ en el número cinco.
students["age"] = students["age"].replace("five", 5)
students["age"]0 4
1 5
2 7
3 8
4 5
5 6
Name: age, dtype: object
En un momento también convertiremos esta en una columna de enteros.
Otro ejemplo en el que el tipo de dato es incorrecto es meal_type. Es una variable categórica con un conjunto conocido de valores posibles. pandas tiene un tipo de dato especial para estos casos:
students["meal_plan"] = students["meal_plan"].astype("category")
students["meal_plan"]0 Lunch only
1 Lunch only
2 Breakfast and lunch
3 Lunch only
4 Breakfast and lunch
5 Lunch only
Name: meal_plan, dtype: category
Categories (2, object): ['Breakfast and lunch', 'Lunch only']
Fíjate en que los valores de la variable meal_type siguen siendo exactamente los mismos, pero el tipo de la variable ha cambiado de object a category.
Es algo tedioso tener que recorrer las columnas una por una con asignaciones de una línea para aplicar el tipo. Una alternativa es pasar un diccionario que asigne tipos a los nombres de columna, como sigue:
students = students.astype({"student_id": "int", "full_name": "string", "age": "int"})
students.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 student_id 6 non-null int64
1 full_name 6 non-null string
2 favourite_food 5 non-null string
3 meal_plan 6 non-null category
4 age 6 non-null int64
dtypes: category(1), int64(2), string(2)
memory usage: 454.0 bytes
Ejercicios
- ¿Qué función usarías para leer un archivo cuyos campos estuvieran separados por “|”?
Leer datos de varios archivos
A veces tus datos están repartidos en varios archivos en lugar de estar en uno solo. Por ejemplo, podrías tener datos de ventas de varios meses, con los datos de cada mes en un archivo distinto: 01-sales.csv para enero, 02-sales.csv para febrero y 03-sales.csv para marzo.
Con pd.read_csv() puedes leer estos datos uno por uno y luego apilarlos en un único dataframe usando la función pd.concat(). Se ve así:
list_of_dataframes = [
pd.read_csv(x)
for x in ["data/01-sales.csv", "data/02-sales.csv", "data/03-sales.csv"]
]
sales_files = pd.concat(list_of_dataframes)
sales_files| month | year | brand | item | n | |
|---|---|---|---|---|---|
| 0 | January | 2019 | 1 | 1234 | 3 |
| 1 | January | 2019 | 1 | 8721 | 9 |
| 2 | January | 2019 | 1 | 1822 | 2 |
| 3 | January | 2019 | 2 | 3333 | 1 |
| 4 | January | 2019 | 2 | 2156 | 9 |
| 5 | January | 2019 | 2 | 3987 | 6 |
| 6 | January | 2019 | 2 | 3827 | 6 |
| 0 | February | 2019 | 1 | 1234 | 8 |
| 1 | February | 2019 | 1 | 8721 | 2 |
| 2 | February | 2019 | 1 | 1822 | 3 |
| 3 | February | 2019 | 2 | 3333 | 1 |
| 4 | February | 2019 | 2 | 2156 | 3 |
| 5 | February | 2019 | 2 | 3987 | 6 |
| 0 | March | 2019 | 1 | 1234 | 3 |
| 1 | March | 2019 | 1 | 3627 | 1 |
| 2 | March | 2019 | 1 | 8820 | 3 |
| 3 | March | 2019 | 2 | 7253 | 1 |
| 4 | March | 2019 | 2 | 8766 | 3 |
| 5 | March | 2019 | 2 | 8288 | 6 |
Si tienes muchos archivos que leer, puede resultar engorroso escribir sus nombres en una lista. En su lugar, puedes usar el paquete glob (incluido en Python) para que encuentre los archivos por ti buscando un patrón en sus nombres. Ten en cuenta que puede haber otros archivos CSV en el directorio data/, así que aquí especificamos "*-sales.csv" para asegurarnos de obtener solo los archivos que incluyen la palabra sales. Aquí, "*" actúa como comodín: representa cualquier secuencia de caracteres.
import glob
list_of_csvs = glob.glob("data/*-sales.csv")
print("List of csvs is:")
print(list_of_csvs, "\n")
sales_files = pd.concat([pd.read_csv(x) for x in list_of_csvs])
sales_filesList of csvs is:
['data/03-sales.csv', 'data/02-sales.csv', 'data/01-sales.csv']
| month | year | brand | item | n | |
|---|---|---|---|---|---|
| 0 | March | 2019 | 1 | 1234 | 3 |
| 1 | March | 2019 | 1 | 3627 | 1 |
| 2 | March | 2019 | 1 | 8820 | 3 |
| 3 | March | 2019 | 2 | 7253 | 1 |
| 4 | March | 2019 | 2 | 8766 | 3 |
| 5 | March | 2019 | 2 | 8288 | 6 |
| 0 | February | 2019 | 1 | 1234 | 8 |
| 1 | February | 2019 | 1 | 8721 | 2 |
| 2 | February | 2019 | 1 | 1822 | 3 |
| 3 | February | 2019 | 2 | 3333 | 1 |
| 4 | February | 2019 | 2 | 2156 | 3 |
| 5 | February | 2019 | 2 | 3987 | 6 |
| 0 | January | 2019 | 1 | 1234 | 3 |
| 1 | January | 2019 | 1 | 8721 | 9 |
| 2 | January | 2019 | 1 | 1822 | 2 |
| 3 | January | 2019 | 2 | 3333 | 1 |
| 4 | January | 2019 | 2 | 2156 | 9 |
| 5 | January | 2019 | 2 | 3987 | 6 |
| 6 | January | 2019 | 2 | 3827 | 6 |
Escribir en un archivo
Así como el patrón típico para leer archivos es pd.read_FILETYPE(), donde el tipo de archivo puede ser, por ejemplo, CSV, todas las formas de escribir dataframes de pandas en disco siguen el patrón DATAFRAME.to_FILETYPE(). Así que, para escribir nuestros datos de ventas en un archivo CSV, el código será sales_files.to_csv(FILEPATH), donde la ruta es la ubicación más el nombre del archivo en el que quieres escribir.
Veamos un ejemplo de cómo escribir datos en un archivo usando nuestros datos de estudiantes, en los que ya hicimos un buen trabajo ajustando los tipos de datos:
students.to_csv("data/students-clean.csv")Ahora volvamos a leerlo y revisemos la información sobre los tipos de datos:
pd.read_csv("data/students-clean.csv").info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Unnamed: 0 6 non-null int64
1 student_id 6 non-null int64
2 full_name 6 non-null object
3 favourite_food 5 non-null object
4 meal_plan 6 non-null object
5 age 6 non-null int64
dtypes: int64(3), object(3)
memory usage: 420.0+ bytes
¿Notas algo? ¡Perdimos gran parte del buen trabajo que hicimos con los tipos de datos! Aunque pandas adivinó que algunas columnas son enteros, perdimos las variables string y categóricas. La razón es que los archivos de texto plano no pueden guardar información contextual (aunque pandas adivinará los tipos de datos de algunas columnas).
Si quieres guardar datos en un archivo y que recuerde los tipos de datos, tendrás que usar otro formato. Para almacenamiento temporal, recomendamos el formato feather, ya que es muy rápido e interoperable con otros lenguajes de programación. La interoperabilidad es una buena razón para evitar formatos de archivo específicos de un lenguaje, como .dta de Stata, .rds de R y .pickle de Python.
Ten en cuenta que el formato feather tiene una dependencia adicional: un paquete llamado pyarrow. Para instalarlo, ejecuta uv add pyarrow en una ventana de terminal.
Este es un ejemplo de cómo escribir en un archivo feather:
students.to_feather("data/students-clean.feather")Ahora volvamos a abrir ese archivo feather y veamos la información asociada.
pd.read_feather("data/students-clean.feather").info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 student_id 6 non-null int64
1 full_name 6 non-null string
2 favourite_food 5 non-null string
3 meal_plan 6 non-null category
4 age 6 non-null int64
dtypes: category(1), int64(2), string(2)
memory usage: 454.0 bytes
Guardar en este formato conservó la información de los tipos de datos.
Leer y escribir otros formatos de datos
La imagen del principio de este capítulo te da una idea de qué otros formatos están disponibles, pero puedes encontrar una lista completa en la documentación oficial de pandas sobre entrada y salida.