====== Pandas ======
Pandas es una herramienta de código abierto para la manipulación y análisis de datos.
* [[https://pandas.pydata.org/|Web oficial]]
Pandas está construido sobre NumPy. Sus estructuras de datos básicas son **Series** y **DataFrame**
===== Instalación =====
En Ubuntu se puede instalar mediante el paquete ''python3-pandas'' y en Arch Linux con ''python-pandas''
También se puede instalar a través de **pip**:
pip3 install pandas
===== Uso =====
Para importar pandas:
import pandas as pd
from pandas import Series, DataFrame
El alias ''pd'' suele ser el que usa la comunidad.
==== Series ====
''Series'' en Python es un objeto similar a un array de una dimensión.
Para crear una serie de pandas a partir de una **lista**:
import pandas as pd
helados = ["Chocolate", "Vainilla", "Fresa", "Limón"]
pd.Series(helados)
# Resultado
# 0 Chocolate
# 1 Vainilla
# 2 Fresa
# 3 Limón
# dtype: object
En la parte izquierda está el índice. No tiene por qué ser índice numérico.
Para crear una serie de pandas a partir de un **diccionario**:
import pandas as pd
webster = {
"Plátano": "Fruta deliciosa",
"Púrpura": "Un color"
}
pd.Series(webster)
# Resultado:
# Plátano Fruta deliciosa
# Púrpura Un color
# dtype: object
Para crear una serie a partir de datos externos, por ejemplo, de un fichero CSV:
import pandas as pd
a = pd.read_csv("ruta/fichero.csv", usecols = ["Titulo1"], squeeze = True)
* ''usecols'': permite indicar qué columna leer del CSV.
* ''squeeze'': si los datos procesados contienen solo una columna, devolverá una Serie en lugar de un objeto DataFrame.
=== Atributos ===
Información sobre las series.
import pandas as pd
about_me = ["Smart", "Handsome", "Charming", "Brilliant", "Humble"]
s = pd.Series(about_me)
* ''*.values'': array de todos los valores de la serie.
* ''*.index'': indica dónde empieza la serie, donde termina y cómo van incrementándose los índices de la serie.
* ''*.dtype'': devuelve el tipo de dato que contiene la serie.
* ''*.is_unique'': indica si todos los elementos son únicos (no se repiten)
* ''*.ndim'': número de dimensiones (las series solo tienen 1 dimensión)
* ''*.shape'': devuelve una tupla con el número de filas y columnas.
* ''*.size''': cuenta el número de elementos.
* ''*.name'': nombre de la serie.
=== Métodos ===
Cálculos o modificaciones sobre la serie.
* ''*.sum()'': suma los elementos de una serie
* ''*.prod()'': multiplicación de todos los elementos de la serie.
* ''*.mean()'': devuelve la media aritmética de los elementos de la serie.
* ''*.head()'': devuelve, por defecto, los primeros 5 elementos de la serie. Entre paréntesis se puede indicar el número de elementos a devolver.
* ''*.tail()'': devuelve, por defecto, los últimos 5 elementos de la serie. Entre paréntesis se puede indicar el número de elementos a devolver.
* ''*.short_values()'': ordena los elementos (pero no sus índices)
* ''*.short_index()'': ordena los índices.
* ''*.count()'': número de elementos (excluye los elementos vacíos o inexistentes).
* ''*.min()'': devuelve el valor más pequeño.
* ''*.max()'': devuelve el valor más grande.
* ''*.median()'': devuelve la mediana (el valor que está en el medio después de ordenar los elementos)
* ''*.mode()'': devuelve el valor que más se repite.
* ''*.describe()'': realiza varias operaciones a la vez.
* ''*.value_counts()'': devuelve el número de veces que aparece un elemento. Parecida a la función PIVOT de Excel.
* ''*.apply(funcion)'': realiza una operación sobre los elementos.
''dtype: object'' es la forma que tiene Pandas de referirse a las cadenas de caracteres (strings)
Si queremos modificar una estructura de Pandas tras aplicarle un método, tenemos que usar el argumento ''inplace'':
numeros = [1.3, 2.8, 10.5, 0.3]
numeros.sort_values(inplace = True)
numeros
# Resultado:
# >>> numeros
# 3 0.3
# 0 1.3
# 1 2.8
# 2 10.5
# dtype: float64
=== Búsqueda ===
Podemos usar la palabra reservada ''in'' para buscar un elemento en una serie
import pandas as pd
numeros = [1, 4, 19, 23]
n = numeros.Series(numeros)
1 in n.values
# Resultado:
# True
Si hacemos ''1 in n'' buscará en los índices, no en los valores
=== Extracción ===
Se utilizan los corchetes para indicar las posiciones de los índices que queremos extraer.
import pandas as pd
numeros = [1, 4, 19, 23]
n = numeros.Series(numeros)
n[2]
# Resultado:
# 19
n[[0, 1, 2]]
# Resultado:
# >>> n[[0, 1, 2]]
# 0 1
# 1 4
# 2 19
# dtype: int64
Se puede utilizar también las etiquetas de índice para hacer las extracciones:
import pandas as pd
paises_ciudades = pd.read_csv("paises_ciudades.csv", index_col = "País", squeeze = True)
paises_ciudades
# Resultado
País
España Madrid
Francia París
España Burgos
Name: Ciudad, dtype: object
paises_ciudades["España"]
# Resultado:
# País
# España Madrid
# España Burgos
# Name: Ciudad, dtype: object
==== Filtrar datos ====
Si queremos quedarnos con las filas cuya columna **Edad** tenga un valor de 18 o más:
print(df.loc[df['Edad'] >= 18])
Podemos encadenar condiciones:
print(df.loc[(df['Edad'] >= 18) & (df['Sexo'] == "Mujer")])
===== DataFrame =====
Las series son estructuras de una dimensión (una columna de datos). Un ''DataFrame'' es una estructura bidimensional, consiste en filas y columnas, como una **tabla**.
{{:informatica:programacion:python:modulos:pandas:pandas-dataframe.png|}}
El tema de las dimensiones viene por el número de referencias que tenemos que dar a la hora de extraer algún valor. Por ejemplo, en las series, nos basta con el índice para indicar que queremos trabajar con el dato que hay en la posición 100. Con los DataFrames esto no es suficiente ya que además de indicar la fila, tenemos que hacer referencia a la columna para indicar unívocamente el dato de interés.
import pandas as pd
Ejemplo de DataFrame a partir de la carga de un fichero CSV:
>>> sales
Region Country Item Type Sales Channel Order Priority Order Date Order ID Ship Date Units Sold Unit Price Unit Cost Total Revenue Total Cost Total Profit
0 Middle East and North Africa Libya Cosmetics Offline M 10/18/2014 686800706 10/31/2014 8446 437.20 263.33 3692591.20 2224085.18 1468506.02
1 North America Canada Vegetables Online M 11/7/2011 185941302 12/8/2011 3018 154.06 90.93 464953.08 274426.74 190526.34
2 Middle East and North Africa Libya Baby Food Offline C 10/31/2016 246222341 12/9/2016 1517 255.28 159.42 387259.76 241840.14 145419.62
3 Asia Japan Cereal Offline C 4/10/2010 161442649 5/12/2010 3322 205.70 117.11 683335.40 389039.42 294295.98
4 Sub-Saharan Africa Chad Fruits Offline H 8/16/2011 645713555 8/31/2011 9845 9.33 6.92 91853.85 68127.40 23726.45
.. ... ... ... ... ... ... ... ... ... ... ... ... ... ...
995 Middle East and North Africa Azerbaijan Snacks Offline C 4/18/2010 534085166 4/25/2010 6524 152.58 97.44 995431.92 635698.56 359733.36
996 Europe Georgia Baby Food Offline H 8/1/2011 590768182 9/7/2011 288 255.28 159.42 73520.64 45912.96 27607.68
997 Middle East and North Africa United Arab Emirates Vegetables Online C 5/12/2011 524363124 6/28/2011 9556 154.06 90.93 1472197.36 868927.08 603270.28
998 Europe Finland Household Offline L 1/25/2016 289606320 2/14/2016 9801 668.27 502.54 6549714.27 4925394.54 1624319.73
999 Europe Portugal Cereal Offline C 4/10/2014 811546599 5/8/2014 3528 205.70 117.11 725709.60 413164.08 312545.52
[1000 rows x 14 columns]
Si aparece ''NaN'' es la forma que tiene Pandas de indicar que falta el valor.
Ejemplo de un DataFrame creado a partir de un diccionario:
purchases = [{'Customer': 'Bob', 'Item': 'Oranges', 'Quantity': 2, 'Unit price': 2},
{'Customer': 'Bob', 'Item': 'Apples', 'Quantity': 3, 'Unit price': 1},
{'Customer': 'Bob', 'Item': 'Milk', 'Quantity': 1, 'Unit price': 4},
{'Customer': 'Alice', 'Item': 'Oranges', 'Quantity': 2, 'Unit price': 2},
{'Customer': 'Alice', 'Quantity': 2, 'Unit price': 3}]
df = DataFrame(purchases)
df
# Resultado:
# Customer Item Quantity Unit price
# 0 Bob Oranges 2 2
# 1 Bob Apples 3 1
# 2 Bob Milk 1 4
# 3 Alice Oranges 2 2
# 4 Alice NaN 2 3
==== Métodos ====
Muchos son los mismos que en las series.
* ''*.head()'': devuelve las 5 primeras líneas del DataFrame.
* ''*.tail()'': devuelve las 5 últimas líneas del DataFrame.
* ''.*min()'': devuelve el valor mínimo de cada columna.
* ''.*max()'': devuelve el valor máximo de cada columna.
* ''*.dtypes()'': devuelve el tipo de dato de cada columna
* ''*.columns()'': devuelve las columnas del DataFrame.
* ''*.columns.values'': devuelve un array de Numpy con el nombre de las columnas del DataFrame.
* ''*.columns.values.tolist()'': devuelve una lista con los nombres de las columnas del DataFrame.
* ''*.value_counts()'': devuelve cuántas veces aparece cierto elemento (como agrupar).
* ''*.info()'': devuelve información sobre el DataFrame
* ''*.describe()'': realiza varias operaciones a la vez.
* ''*.nunique()'': devuelve los valores únicos de una columna.
La mayoría de las funciones de DataFrames devuelven un objeto **nuevo** de pandas. Si queremos realizar una operación y que se modifique el propio objeto, debemos pasarle el argumento ''inplace=True'')
>>> sales.info()
RangeIndex: 1000 entries, 0 to 999
Data columns (total 14 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Region 1000 non-null object
1 Country 1000 non-null object
2 Item Type 1000 non-null object
3 Sales Channel 1000 non-null object
4 Order Priority 1000 non-null object
5 Order Date 1000 non-null object
6 Order ID 1000 non-null int64
7 Ship Date 1000 non-null object
8 Units Sold 1000 non-null int64
9 Unit Price 1000 non-null float64
10 Unit Cost 1000 non-null float64
11 Total Revenue 1000 non-null float64
12 Total Cost 1000 non-null float64
13 Total Profit 1000 non-null float64
dtypes: float64(5), int64(2), object(7)
memory usage: 109.5+ KB
Si queremos mostrar un resumen de operaciones habituales, pero solo con los valores que no son numéricos, pasamos las opción ''include'' con el valor ''O'':
df.describe(include = "O")
==== Extraer columnas ====
Podemos coger una columna por su nombre:
sales.Country
Se creará una serie.
Si la columna tiene un espacio, entonces es mejor hacer referencia por la etiqueta:
sales["Client Name"]
Para extraer más de una columna:
sales[ ["Client Name", "Salary"] ]
Como extraemos más de una columna, el objeto resultante será un DataFrame y no una serie.
==== Añadir filas ====
Si queremos añadir una fila al final del DataFrame:
df.append({"Nombre": "Pepito", "Año": 2000, "Edad": 20}, ignore_index=True)
Podemos añadir un DataFrame en otro:
df2.append(df1, ignore_index=True)
==== Añadir columnas ====
sales["City"] = "Madrid"
Se creará una nueva columna llamada "City" con el valor "Madrid" en todas sus filas.
Si queremos indicar la posición de la nueva columna, utilizaremos el método ''insert()'':
sales.insert(3, column = "City", value = "Madrid")
Con el anterior código decimos que la nueva columna "City" ocupará la posición 3 (será entonces la columna 4) y todos sus valores serán "Madrid".
También se pueden crear columnas calculadas:
sales["Salary+Bonus"] = sales["Salary"] + sales["Bonus"]
==== Eliminar filas ====
Utilizamos el método ''drop()''
# Eliminar la fila que ocupa la posición 10:
new_sales = sales.drop(10)
Si queremos que se modifique el DataFrame en lugar de hacer una copia de él:
# Eliminar la fila que ocupa la posición 10:
sales.drop(10, inplace=True)
==== Eliminar columnas ====
Utilizamos también el método ''drop()'', pero con el argumento ''axis'' a 1 para indicar que queremos eliminar columnas:
# Eliminar la columna que ocupa la posición 10:
sales.drop(10, axis = 1)
También podemos indicar el nombre de la columna en lugar de la posición: ''sales.drop(%%"%%Nacionalidad%%"%%, axis=1)''
También podemos borrarla utilizando la función ''del'':
del sales["Region"]
También podemos eliminarla con método ''pop()'':
sales.pop("Region")
La operación ''pop()'' es permanente, es decir, modifica el DataFrame sin necesidad del argumento ''inplace''
==== Eliminar filas vacías ====
Utilizamos el método ''dropna()'' para eliminar las filas que contienen algún valor ''NaN''.
sales.dropna()
Para eliminar solo las filas completamente vacías:
sales.dropna(how = "all")
==== Eliminar columnas vacías ====
Utilizamos también el método ''dropna()'' pero cambiando el eje:
sales.dropna(axis = 1)
# También se podría indicar así:
# sales.dropna(axis = "columns")
Podemos indicar las columnas a revisar:
sales.dropna(subset = ["Salary", "Country"])
==== Rellenar vacíos (NaN) ====
Para aquellas "celdas" donde no hay datos (y Pandas indica con ''NaN''), podemos indicar qué valor:
# Rellenar NaN con 0:
sales.fillna(0)
Podemos ir columna a columna:
sales["Salary"].fillna(0, inplace = True)
''inplace'' indica si el objeto al que se aplica el método se modificará.
==== Reemplazar ====
Para sustituir un valor por otro, utilizamos el método ''replace()'':
sales.replace("Espana", "España")
A este método se ls puede pasar un diccionario para realice diferentes sustituciones a la vez:
sales.replace({"Espana": "España", "madrid": "Madrid"})
==== Cambiar tipos de datos ====
Para ver el tipo de dato de cada columna, utilizamos ''dtype()'':
sales.dtypes
# Resultado
#>>> sales.dtypes
#Region object
#Country object
#Item Type object
#Sales Channel object
#Order Priority object
#Order Date object
#Order ID int64
#Ship Date object
#Units Sold int64
#Unit Price float64
#Unit Cost float64
#Total Revenue float64
#Total Cost float64
#Total Profit float64
#dtype: object
Para modificar el tipo de dato que contiene cada columna se puede usar el método ''astype()'':
sales["Unit Price"] = sales["Unit Price"].astype("int")
Para poder utilizar este método, es necesario que todas las filas tengan valores, es decir, que no exista ningún ''NaN''
Si queremos pasar una fecha al formato ISO:
sales["Date"] = sales.to_datetime(sales["Date"])
Si el formato original de fecha no es estándar o Pandas no lo coge correctamente, podemos indicárselo con la opción ''format'':
sales["Date"] = sales.to_datetime(sales["Date"], format="%d/%m/%Y")
==== Ordenación ====
Como un DataFrame es una estructura de dos dimensiones, debemos indicar la columna por la que queremos ordenar:
sales.sort_values(by="Salary")
Estaríamos ordenando todo el DataFrame en base a los valores de la columa "Salary".
Si queremos ordenar en base a más de 1 columna:
sales.sort_values(["Client Name", "Salary"], ascending = [True, False])
Estamos ordenando "Client Name" en orden alfabético y "Salary" descendentemente.
==== Filtrar datos ====
Podemos filtrar por varios métodos:
* Posición
* ''where()''
* ''query()''
Si queremos quedarnos con las filas que tengan "Europe" como valor de la columna "Region":
filtro = sales["Region"] == "Europe"
sales[filtro]
filtro = sales["Salary"] > 10000
sales[filtro]
Se pueden usar más de una condición para filtrar datos:
filtro1 = sales["Region"] == "Europe"
filtro2 = sales["Salary"] > 10000
sales[filtro1 & filtro2]
* ''&'': AND. Deben cumplirse todas las condiciones para obtener una verdad.
* ''|'': OR. Si una de las condiciones es cierta, el total será verdad.
Si vamos a combinar varias condiciones con ''|'' es recomendable utilizar el método ''isin'':
filtro = sales["Region"].isin(["Europe", "Asia"])
sales[filtro]
Estamos buscando las filas cuya "Region" sea "Europe" o "Asia".
Si queremos buscar por un rango de valores, utilizamos el método ''between'':
filtro = sales["Salary"].between(20000, 50000)
sales[filtro]
Los valores límites también están incluidos en el filtro, es decir, para el ejemplo anterior, también devolvería las filas cuyo salario fuese 20000 y 50000.
También podemos filtrar utilizando el método ''where()'':
filtro = sales["Salary"].between(20000, 50000)
sales.where(filtro)
La diferencia es que devuelve el DataFrame entero, pero solo están cubiertas las filas que cumplen la condición, el resto contienen ''NaN''
Por último, el método ''query()'' se supone que es más eficiente para los filtrados:
sales.query("Country == 'Canada'")
Otro ejemplo:
sales.query("Country in ['Canada', 'Japan']")
Si queremos combinar condiciones:
sales.query("Country == 'Canada' and Salary > 10000")
Para poder usar el método ''query()'' las columnas no deben contener espacios.
=== Vacíos ===
Para filtrar por vacíos:
filtro = sales["Region"].isnull()
# Devuelve todas las filas donde "Region" esté vacío (NaN)
sales[filtro]
Lo contrario se haría con ''notnull()'':
filtro = sales["Region"].notnull()
# Devuelve todas las filas donde "Region" no esté vacío:
sales[filtro]
=== Duplicados ===
El método ''drop_duplicates()'' se usa para eliminar duplicados:
# Quedarnos con los valores únicos:
sales.drop_duplicates(subset = ["Item Type"], keep = "first")
Para contar los valores únicos, utilizamos el método ''unique()'':
sales["Region"].unique()
# Ejemplo de salida:
#array(['Middle East and North Africa', 'North America', 'Asia',
# 'Sub-Saharan Africa', 'Europe',
# 'Central America and the Caribbean', 'Australia and Oceania'],
# dtype=object)
Para contarlos, utilizamos el método ''nunique'':
sales["Region"].nunique(dropna = False)
# Resultado:
# 7
* ''dropna'' indica si queremos descartar los valores vacíos (NaN)
==== Extracción ====
=== Por posición del íncide ===
Utilizamos el método ''iloc()'':
# Obtener las filas desde el índice 0 al 9
sales.iloc[0:10]
# Obtener las filas del índice 0 y del 2
sales.iloc[[0,2]]
Podemos hacer la intersección con el segundo argumento:
sales.iloc[14, 2]
# Devuelve el contenido del índice 14 en su tercera (posición 2) columna.
=== Por etiqueta del índice ===
Si queremos establecer los valores de cierta columna como índice del DataFrame:
sales.set_index("Order ID", inplace = True)
Si queremos volver a dejar los índices por defecto:
sales.reset_index(inplace = True)
Ahora podemos extraer datos en base a la etiqueta del índice:
sales.set_index("Region", inplace = True)
sales.loc["Europe"]
Podemos hacer la intersección indicando un segundo argumento al método ''loc()'':
sales.loc["Europe", "Item Type"]
# Devuelve las filas cuya "Region" sea "Europe" y su valor de "Item Type"
=== Extracción al azar ===
Para extraer filas al azar, se puede utilizar el método ''sample()'':
# Extrae una fila al azar:
sales.sample()
Podemos indicar el número de filas a extraer:
# Extraer 10 muestras al azar:
sales.sample(n = 10)
Incluso podríamos extraer por fracciones del total:
# Extraer una muestra del 25 % del total:
sales.sample(frac = .25)
Si quisiéramos extraer al azar columnas en lugar de filas, añadimos el argument ''axis'' con el valor ''1'' o ''columns'':
# Extraer 3 columnas al azar:
sales.sample(n = 3, axis = "columns")
=== Máximos ===
Para extraer los N valores más grandes, utilizamos el método ''nlargest()'':
# Las 3 filas con los valores de la columna "Units Sold" más grandes:
sales.nlargest(3, columns = "Units Sold")
Otra forma:
sales["Units Sold"].nlargest(3)
=== Mínimos ===
Para extraer los N valores más pequeños, utilizamos el método ''nsmallest()'':
# Las 2 filas con los valores de la columna "Units Sold" más pequeños:
sales.nsmallest(2, columns = "Units Sold")
Otra forma:
sales["Units Sold"].nsmallest(2)
==== Modificar valores ====
sales.iloc[10, 2] = "Hola"
Cambiamos el valor que ya en la fila 10, columna 2 por el texto "Hola".
Podemos cambiar una serie de valores. Por ejemplo, donde "Region" sea "Europa", quiero que ahora valga "Europa":
es_europa = sales["Region"] == "Europe"
sales.loc[es_europa, "Region"] = "Europa"
==== Trabajando con texto ====
Modificando cadenas con ''str.replace()'':
sales["Region"] = sales["Region"].str.replace("Europe", "Europa")
# Elimina el símbolo '€' y cambia el tipo de dato de la columna 'Salary' a decimal.
sales["Salary"] = sales["Salary"].str.replace("€", "").astype("float")
Eliminando espacios:
# Elimina espacios a la derecha
sales["Region"] = sales["Region"].str.rstrip()
# Elimina espacios a la izquierda
sales["Region"] = sales["Region"].str.lstrip()
# Elimina espacios a los dos lados:
sales["Region"] = sales["Region"].str.strip()
=== Filtrando por texto ===
# Devuelve todos los valores de "Region" que contengan 'rope'
filtro = sales["Region"].str.lower().str.contains("eur")
sales[filtro]
# Devuelve todos los valores de "Region" que empiecen por 'eur'
filtro = sales["Region"].str.lower().str.startswith("eur")
sales[filtro]
==== Unión de DataFrames ====
* ''concat()''
* Inner join
* ''merge()''
# Unión de dos DataFrame que comparten estructura:
clientes = pd.concat(objs = [cliente1, cliente2], ignore_index = True)
''ignore_index'' por defecto es ''False'', así que cogerá los índices originales de cada DataFrame concatenado
Para realizar inner joins:
cliente1.merge(cliente2, how = "inner", on = "Customer ID")
Para los left joins:
cliente1.merge(cliente2, how = "left", on = "Customer ID")
==== Agrupar ====
dfg = df.groupby("Region")
Esto creará un objeto ''DataFrameGroupBy'' y podremos usar en él los métodos típicos:
* ''sum()''
* ''mean()''
* ''median()''
* ''min()''
* ''max()''
Se puede agrupar por más de un campo poniéndolos entre corchetes:
dfg = df.groupby(["Region", "Country"])
===== Fechas =====
Cuando cargamos un fichero a un DataFrame y contiene alguna fecha, lo indicamos con el argumento ''parse_dates'' para poder así luego operar con objetos de tipo fecha en lugar de strings:
pd.read_csv("ventas.csv", index_col = "ID", parse_dates = ["fecha_pedido", "fecha_envio"])
==== Timestamp ====
pd.Timestamp("2020-12-31 00:01:59")
pd.Timestamp("2020/12/31 00:01:59")
pd.Timestamp(2020, 12, 31, 0, 1, 59)
==== date_range() ====
Genera fechas dentro de un rango:
pd.date_range(start = "2020-12-01", end = "2020-12-31", freq = "D")
# Resultado
#DatetimeIndex(['2020-12-01', '2020-12-02', '2020-12-03', '2020-12-04',
# '2020-12-05', '2020-12-06', '2020-12-07', '2020-12-08',
# '2020-12-09', '2020-12-10', '2020-12-11', '2020-12-12',
# '2020-12-13', '2020-12-14', '2020-12-15', '2020-12-16',
# '2020-12-17', '2020-12-18', '2020-12-19', '2020-12-20',
# '2020-12-21', '2020-12-22', '2020-12-23', '2020-12-24',
# '2020-12-25', '2020-12-26', '2020-12-27', '2020-12-28',
# '2020-12-29', '2020-12-30', '2020-12-31'],
# dtype='datetime64[ns]', freq='D')
* ''freq'': cómo se generan las fechas. **D** un día; **2D**, dos días; **W** para domingos. Incrementa horas con **H**; **M** para meses; **B** para días laborales.
Podemos especificar la cantidad de fechas con la opción ''periods'':
# Generar 10 fechas desde el 2020-12-01
pd.date_range(start = "2020-12-01", periods = 10, freq = "D"
==== Timedelta ====
Medida de tiempo, es un momento en el tiempo, como una fecha.
time_a = pd.Timestamp("2020-12-10 04:35:06")
time_b = pd.Timestamp("2020-12-01 12:22:58")
time_a - time_b
# Resultado:
# Timedelta('8 days 16:12:08')
Para crear un objeto de este tipo:
pd.Timedelta(days = 3, hours = 12, minutes = 45)
# Resultado
# Timedelta('3 days 12:45:00')
===== Entrada de datos =====
==== CSV ====
Podemos cargar también desde una URL:
url = "direccion/fichero.csv"
pd.read_csv(url)
Si no queremos coger la cabecera del fichero (normalmente contendrá el nombre de los campos/columnas):
pd.read_csv("fichero.csv", header = None)
Si los datos están separados por otro caracter que no sea la coma, lo indicamos como parámetro:
pd.read_csv("fichero.csv", sep = ';')
Si estuviesen separados por espacios, pero no sabemos cuántos, utilizamos una expresión regular:
pd.read_csv("fichero.csv", sep = '\s+')
==== Excel ====
Para la lectura de ficheros de Excel es necesario el módulo ''xlrd'':
pip3 install xlrd
Si vamos a leer un fichero que no sea ''.xls'' (''.xlsx'', por ejemplo), será mejor utilizar ''openpyxl'' ya que en últimas versiones de xlrd [[https://groups.google.com/g/python-excel/c/IRa8IWq_4zk/m/Af8-hrRnAgAJ?pli=1|van a dejar de dar soporte a formatos que no sean ''.xls'']].
pd.read_excel("fichero.xlsx")
Si queremos leerlo con ''openpyxl'':
pd.read_excel("fichero.xlsx", engine="openpyxl")
Si tuviésemos más de una hoja, podríamos cargar solo la que quisiéramos:
pd.read_excel("fichero.xlsx", sheet_name = "Hoja 2")
Si de una hoja solo queremos ciertas columnas:
pd.read_excel("fichero.xlsx", sheet_name = "Hoja 2", usecols = "A,L,M:AA")
En el ejemplo anterior solo leería las columnas A, L y de la M hasta la AA, ambas incluidas.
Si quisiéramos cargar varias hojas a la vez, el resultado sería un diccionario donde cada clave apuntaría a un dataframe, uno por hoja.
=== Saltar filas ===
Si a la hora de leer un fichero Excel queremos descartar ciertas filas:
# Cargamos un fichero Excel como un DataFrame de Pandas,
# saltándonos las 3 primeras filas
pd.read_excel("fichero.xlsx", skiprows=[0,1,2]
===== Salida de datos =====
==== CSV ====
pd.to_csv("salida.csv", index = False)
* ''index'' indica si el índice aparecerá en el fichero resultante.
Podemos indicar qué columnas exportar:
pd.to_csv("salida.csv", index = False, columns = ["Nombre", "Apellidos", "Edad"])
Si queremos forzar la codificación, utilizamos el parámetro ''encoding'':
pd.to_csv("salida.csv", index = False, encoding = "utf-8")
Si queremos cambiar el separador de campos (por defecto es una coma):
pd.to_csv("salida.csv", sep=";")
Si vamos a guardar datos con decimales, podemos indicar la precisión (número de decimales) con la que queremos que se guarden:
pd.to_csv("salida.csv", index=False, float_format="%0.4f")
Con el código anterior, indicamos que los números decimales tengan 4 decimales.
==== Excel ====
Para la escritura de ficheros de Excel es necesario el módulo ''openpyxl'':
pip3 install openpyxl
# Creamos un contenedor para poder guardar finalmente un fichero Excel
output = pd.ExcelWriter("salida.xlsx")
# Escribimos el DataFrame en el contenedor Excel
ventas.to_excel(output, sheet_name = "Ventas", index = False)
# Escribimos el fichero
output.save()
Podríamos elegir qué columnas grabar:
ventas.to_excel(output, sheet_name = "Ventas", index = False, columns = ["Fecha", "Precio"])
===== Opciones =====
==== Número de resultados ====
Filas a mostrar:
import pandas as pd
# Número de filas que mostrará Pandas de un DataFrame:
pd.options.display.max_rows
# Establecer el número de filas a mostrar:
pd.options.display.max_rows = 10
Columnas a mostrar:
import pandas as pd
# Número de columnas que mostrará Pandas de un DataFrame:
pd.options.display.max_columns
# Establecer el número de columnas a mostrar:
pd.options.display.max_columns= 3
También podemos modificar las opciones utilizando métodos en lugar de atributos:
# Obtener el valor de las opciones
pd.get_option("max_rows")
pd.get_option("max_columns")
# Modificar las opciones
pd.set_option("max_rows", 10)
pd.set_option("max_columns", 3)
Para reiniciar las opciones a sus valores iniciales:
pd.reset_option("max_rows")
pd.reset_option("max_columns")
Podemos saber más acerca de una opción mediante el método ''describe_option()''. Por ejemplo, ''pd.describe_option(%%"%%max_columns%%"%%)''
==== Precisión numérica ====
Por precisión nos referimos al número de decimales después del separador de decimales.
Por defecto, Pandas muestra 6 decimales.
pd.get_option("precision")
# Resultado:
# 6
Para establecer otra precisión:
pd.set_option("precision", 2)
===== Ejemplos =====
==== Combinar ficheros Excel ====
El siguiente fragmento de código coge todos los ficheros ''.xlsx'' que hay en el directorio de trabajo y los une en un único documento llamado ''final.xlsx'' que será la unión de todos ellos:
import pandas as pd
import glob
all_data = pd.DataFrame()
for f in glob.glob("*.xlsx"):
df = pd.read_excel(f)
all_data = all_data.append(df, ignore_index=True)
# Volcamos el DataFrame resultante en un documento Excel
all_data.to_excel("final.xlsx", index=False)
==== Filtrar registros Excel ====
El siguiente código abre el documento ''demo.xlsx'', filtra los registros por los valores de dos columnas y vuelca el resultado en el fichero ''demo_mod.xlsx'':
import os
import pandas as pd
df = pd.read_excel("demo.xlsx")
# Filtrado por columna
output = df.loc[(df['Age'] >= 50) & (df['Country'] == "United States")]
output.to_excel("demo_mod.xlsx", index=False)
==== Titanic ====
Partiendo de un CSV con información sobre pasajeros:
import pandas as pd
fname = '~/data/titanic/train.csv'
data = pd.read_csv(fname)
Ver si hay campos vacíos:
data.count()
Valores mínimos y máximos de edad:
data['Age'].min(), data['Age'].max()
Distribución de valores de supervivientes:
data['Survived'].value_counts()
En términos porcentuales:
data['Survived'].value_counts() * 100 / len(data)
Distribución por sexo:
data['Sex'].value_counts()
Distribución por tipo de pasajero:
data['Pclass'].value_counts()
Representación gráfica (en notebook de Jupyter):
%matplotlib inline
alpha_color = 0.5
# Distribución se supervivientes
data['Survived'].value_counts().plot(kind='bar')
Distribución gráfica por sexo:
data['Sex'].value_counts().plot(kind='bar',
color=['b', 'r'],
alpha=alpha_color)
Distribución gráfica por clase de pasajeros:
data['Pclass'].value_counts().sort_index().plot(kind='bar',
alpha=alpha_color)
Comparando gráficamente supervivientes con edades:
data.plot(kind='scatter', x='Survived', y='Age')
Comparando gráficamente supervivientes con tramos de edades:
bins = [0, 10, 20, 30, 40, 50, 60, 70, 80]
data['AgeBin'] = pd.cut(data['Age'], bins)
data[data['Survived'] == 1]['AgeBin'].value_counts().sort_index().plot(kind='bar')
Gráficamente los que no sobrevivieron:
data[data['Survived'] == 0]['AgeBin'].value_counts().sort_index().plot(kind='bar')
data['AgeBin'].value_counts().sort_index().plot(kind='bar')
Pasajeros de primera clase que sobrevivieron:
data[data['Pclass'] == 1]['Survived'].value_counts().plot(kind='bar')
Pasajeros de tercera clase que sobrevivieron:
data[data['Pclass'] == 3]['Survived'].value_counts().plot(kind='bar')
Pasajeros de género masculino que sobrevivieron:
data[data['Sex'] == 'male']['Survived'].value_counts().plot(kind='bar')
Pasajeros de género femenino que sobrevivieron:
data[data['Sex'] == 'female']['Survived'].value_counts().plot(kind='bar')
Pasajeros de género masculino y primera clase que sobrevivieron:
data[(data['Sex'] == 'male') & (data['Pclass'] == 1)]['Survived'].value_counts().plot(kind='bar')
Pasajeros de género masculino y tercera clase que sobrevivieron:
data[(data['Sex'] == 'male') & (data['Pclass'] == 3)]['Survived'].value_counts().plot(kind='bar')
Pasajeros de género femenino y primera clase que sobrevivieron:
data[(data['Sex'] == 'female') & (data['Pclass'] == 1)]['Survived'].value_counts().plot(kind='bar')
Pasajeros de género femenino y tercera clase que sobrevivieron:
data[(data['Sex'] == 'female') & (data['Pclass'] == 3)]['Survived'].value_counts().plot(kind='bar')
===== Recursos =====
* https://github.com/bonzanini/VideoCourse-DataAnalysisPython