Manejo de datos con Pandas
1. Resumen
Vamos a ver una pequeña introducción a la librería de manipulación y análisis de datos Pandas
. Con ella podremos manejar y procesar datos tabulares que nos ayudará para poder operar con ellos y obtener información de una manera muy valiosa
2. ¿Qué es Pandas?
Pandas es una librería de Python que está diseñada para que el trabajo con datos relacionales o etiquetados sea fácil e intuitivo
Pandas está diseñado para muchos tipos diferentes de datos:
- Datos tabulares con columnas de tipos heterogéneos, como en una tabla SQL o una hoja de cálculo de Excel
- Datos de series de tiempo ordenados y desordenados (no necesariamente de frecuencia fija).
- Datos matriciales arbitrarios (homogéneos o heterogéneos) con etiquetas de fila y columna
- Cualquier otra forma de conjuntos de datos observacionales/estadísticos. No es necesario etiquetar los datos en absoluto para colocarlos en una estructura de datos de pandas.
Las dos estructuras de datos principales de Pandas son las Serie
s (unidimensional) y los DataFrame
s (bidimensional). Pandas está construido sobre NumPy y está destinado a integrarse bien dentro de un entorno informático científico con muchas otras bibliotecas de terceros.
Para los científicos de datos, el trabajo con datos generalmente se divide en varias etapas: recopilar y limpiar datos, analizarlos/modelarlos y luego organizar los resultados del análisis en una forma adecuada para trazarlos o mostrarlos en forma de tabla. pandas es la herramienta ideal para todas estas tareas.
Otra característica es que pandas es rápido, muchos de los algoritmos de bajo nivel se han construido en C
.
2.1. Pandas como pd
Generalmente a la hora de importar pandas se suele importar con el alias de pd
import pandas as pdprint(pd.__version__)
1.0.1
3. Estructuras de datos de Pandas
En Pandas existen dos tipos de estructuras de datos: las Serie
s y los DataFrame
s
3.1. Series
El tipo de dato Serie
es una matriz etiquetada unidimensional capaz de contener cualquier tipo de datos (enteros, cadenas, números de punto flotante, objetos Python, etc.). Está dividida en índices.
Para crear un tipo de dato Serie
la forma más común es
serie = pd.Series(data, index=index)
Donde data
puede ser
- Un diccionario
- Una lista o tupla
- Un ndarray de Numpy
- Un valor escalar
Como uno de los tipos de datos puede ser un ndarray de NumPy, importamos NumPy para poder usarlo
import numpy as np
3.1.1. Series desde un diccionario
import numpy as npdiccionario = {"b": 1, "a": 0, "c": 2}serie = pd.Series(diccionario)serie
b 1a 0c 2dtype: int64
Si se pasa un índice, se extraerán los valores de los datos correspondientes a las etiquetas del índice. Si no existen, se crean como NaN
(not a number)
diccionario = {"b": 1, "a": 0, "c": 2}serie = pd.Series(diccionario, index=["b", "c", "d", "a"])serie
b 1.0c 2.0d NaNa 0.0dtype: float64
3.1.2. Series desde una lista o tupla
Si los datos provienen de una lista o tupla y no se pasa ningún índice, se creará uno con valores [0, ..., len(data)-1]
serie = pd.Series([1, 2, 3, 4])serie
0 11 22 33 4dtype: int64
Si se pasa un índice, este debe tener la misma longitud que los datos
serie = pd.Series([1, 2, 3, 4], index=["a", "b", "c", "d"])serie
a 1b 2c 3d 4dtype: int64
3.1.3. Series desde un ndarray
Si los datos provienen de un ndarray y no se pasa ningún índice, se creará uno con valores [0, ..., len(data)-1]
serie = pd.Series(np.random.randn(5))serie
0 1.2678651 -0.8778572 -0.1385563 -0.1329874 -0.827295dtype: float64
Si se pasa un índice, este debe tener la misma longitud que los datos
serie = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])serie
a -1.091828b -0.584243c 0.220398d 1.248923e 1.652351dtype: float64
3.1.4. Series desde un escalar
Si se crea la serie desde un escalar, se creará con un único ítem
serie = pd.Series(5.0)serie
0 5.0dtype: float64
Si se quieren crear más ítems en la serie, hay que pasarle el índice con el número de ítems que se quiere, de esta manera todos los ítems tendrán el valor del escalar
serie = pd.Series(5.0, index=["a", "b", "c", "d", "e"])serie
a 5.0b 5.0c 5.0d 5.0e 5.0dtype: float64
3.1.5. Operaciones con Series
Al igual que con Numpy, podemos realizar operaciones con todos los elementos de una serie, sin tener que hacer una iteración por cada uno de ellos
serie = pd.Series(5.0, index=["a", "b", "c", "d", "e"])print(f"serie:\n{serie}")print(f"\nserie + serie =\n{serie + serie}")
serie:a 5.0b 5.0c 5.0d 5.0e 5.0dtype: float64serie + serie =a 10.0b 10.0c 10.0d 10.0e 10.0dtype: float64
serie = pd.Series(5.0, index=["a", "b", "c", "d", "e"])print(f"serie:\n{serie}")print(f"\nexp(serie) =\n{np.exp(serie)}")
serie:a 5.0b 5.0c 5.0d 5.0e 5.0dtype: float64exp(serie) =a 148.413159b 148.413159c 148.413159d 148.413159e 148.413159dtype: float64
Una diferencia entre Serie
s y ndarrays es que las operaciones entre Serie
s alinean automáticamente los datos según sus etiquetas. Por lo tanto, se pueden escribir cálculos sin tener en cuenta si las Serie
s involucradas tienen las mismas etiquetas. Si no se encuentra una etiqueta en una Serie
u otra, el resultado se marcará como faltante (NaN).
serie = pd.Series(5.0, index=["a", "b", "c", "d", "e"])print(f"serie:\n{serie}")print(f"\nserie[1:] + serie[:-1] =\n{serie[1:] + serie[:-1]}")
serie:a 5.0b 5.0c 5.0d 5.0e 5.0dtype: float64serie[1:] + serie[:-1] =a NaNb 10.0c 10.0d 10.0e NaNdtype: float64
3.1.6. Atributo nombre de las Series
Uno de los atributos de las Serie
s es name
, el cual corresponde al nombre que tendrán cuando se añadan a un DataFrame. Por el camino contrario, cuando se obtiene una serie de un DataFrame, esta serie tendrá como nombre el que tenía en el DataFrame
serie = pd.Series(np.random.randn(5), name="aleatorio")serie
0 -0.1910091 -0.7931512 -0.9077473 -1.4405084 -0.676419Name: aleatorio, dtype: float64
Se puede cambiar el nombre de una serie mediante el método rename()
serie = serie.rename("random")serie
0 -0.1910091 -0.7931512 -0.9077473 -1.4405084 -0.676419Name: random, dtype: float64
3.2. DataFrames
Un DataFrame
es una estructura de datos etiquetada y bidimensional, con columnas de tipos potencialmente diferentes, es decir, en una columna puede haber datos de tipo entero, en otra columna datos de tipo string, etc. Puede pensar en ello como una hoja de cálculo o una tabla SQL, o un diccionario de objetos Serie
s.
Es el objeto pandas más utilizado. Al igual que las Serie
s, los DataFrame
s aceptan muchos tipos diferentes de entrada:
Junto con los datos, opcionalmente puede pasar argumentos de índice (etiquetas de fila) y columnas (etiquetas de columna). Si pasa un índice y/o columnas, está garantizando el índice y/o columnas del DataFrame
resultante. Por lo tanto, un diccionario de Serie
s más un índice específico descartará todos los datos que no coincidan con el índice pasado
Si no se pasan las etiquetas de los ejes, se construirán a partir de los datos de entrada basándose en reglas de sentido común.
3.2.1. DataFrames desde un diccionario de Series
Si se pasa un diccionario con Serie
s se creará el DataFrame
con tantas columnas como Serie
s tenga el diccionario
diccionario = {
"uno": pd.Series([1.0, 2.0, 3.0]),
"dos": pd.Series([4.0, 5.0, 6.0, 7.0])
}
dataframe = pd.DataFrame(diccionario)
dataframe
Si cada una de las Serie
s tiene índices definidos, el DataFrame
resultante será la unión de estos índices
diccionario = {
"uno": pd.Series([1.0, 2.0, 3.0], index=["a", "b", "c"]),
"dos": pd.Series([4.0, 5.0, 6.0, 7.0], index=["a", "b", "c", "d"])
}
dataframe = pd.DataFrame(diccionario)
dataframe
dataframe = pd.DataFrame(diccionario, index=["d", "b", "a"])
dataframe
Si se le pasan las columnas aparecerán en el orden pasado
dataframe = pd.DataFrame(diccionario, columns=["dos", "tres"])
dataframe
3.2.2. DataFrames desde un diccionario de ndarrays o listas
Todos los ndarrays o listas deben tener la misma longitud. Si se pasa un índice, también debe tener la misma longitud que los ndarrays o listas
diccionario = {
"uno": [1.0, 2.0, 3.0, 4.0],
"dos": [4.0, 3.0, 2.0, 1.0]
}
dataframe = pd.DataFrame(diccionario)
dataframe
3.2.3. DataFrames desde una matriz
Si se pasa un índice, tiene que tener la misma longitud que el número de filas de la matriz y si se pasan las columnas, tienen que tener la misma longitud que las columnas de la matriz
matriz = np.array([[1, 3], [2, 2], [3, 1]])
dataframe = pd.DataFrame(matriz, index=["a", "b", "c"], columns=["columna1", "columna2"])
dataframe
3.2.4. DataFrames desde una lista de diccionarios
lista = [{"a": 1, "b": 2}, {"a": 5, "b": 10, "c": 20}]
dataframe = pd.DataFrame(lista)
dataframe
3.2.5. DataFrames desde un diccionario de tuplas
diccionario = {
("a", "b"): {("A", "B"): 1, ("A", "C"): 2},
("a", "a"): {("A", "C"): 3, ("A", "B"): 4},
("a", "c"): {("A", "B"): 5, ("A", "C"): 6},
("b", "a"): {("A", "C"): 7, ("A", "B"): 8},
("b", "b"): {("A", "D"): 9, ("A", "B"): 10},
}
dataframe = pd.DataFrame(diccionario)
dataframe
3.2.6. DataFrames desde una Serie
El resultado será un DataFrame
con el mismo índice que la Serie de entrada, y con una columna cuyo nombre es el nombre original de la Serie (solo si no se proporciona otro nombre de columna).
diccionario = {"b": 1, "a": 0, "c": 2}
serie = pd.Series(diccionario)
dataframe = pd.DataFrame(serie)
dataframe
4. Exploración de un DataFrame
Cuando un DataFrame
es muy grande no se puede representar entero
california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")
california_housing_train
Por lo que es muy útil tener métodos para explorarlo y obtener información de manera rápida.
4.1. Cabeza del DataFrame
Para ver las primeras filas y hacerse una idea de cómo es el DataFrame
existe el método head()
, que por defecto muestra las primeras 5 filas del DataFrame
. Si se quiere ver un número distinto de filas introducirlo mediante el atributo n
california_housing_train.head(n=10)
4.2. Cola del DataFrame
Si lo que se quiere es ver las últimas filas se puede usar el método tail()
, mediante el atributo n
se elige cuántas filas mostrar
california_housing_train.tail()
4.3. Información del DataFrame
Otro método muy útil es info()
que nos da información del DataFrame
diccionario = {"uno": pd.Series([1.0, 2.0, 3.0]),"dos": pd.Series([4.0, 5.0, 6.0, 7.0])}dataframe = pd.DataFrame(diccionario)dataframediccionario = {"uno": pd.Series([1.0, 2.0, 3.0], index=["a", "b", "c"]),"dos": pd.Series([4.0, 5.0, 6.0, 7.0], index=["a", "b", "c", "d"])}dataframe = pd.DataFrame(diccionario)dataframedataframe = pd.DataFrame(diccionario, index=["d", "b", "a"])dataframedataframe = pd.DataFrame(diccionario, columns=["dos", "tres"])dataframediccionario = {"uno": [1.0, 2.0, 3.0, 4.0],"dos": [4.0, 3.0, 2.0, 1.0]}dataframe = pd.DataFrame(diccionario)dataframematriz = np.array([[1, 3], [2, 2], [3, 1]])dataframe = pd.DataFrame(matriz, index=["a", "b", "c"], columns=["columna1", "columna2"])dataframelista = [{"a": 1, "b": 2}, {"a": 5, "b": 10, "c": 20}]dataframe = pd.DataFrame(lista)dataframediccionario = {("a", "b"): {("A", "B"): 1, ("A", "C"): 2},("a", "a"): {("A", "C"): 3, ("A", "B"): 4},("a", "c"): {("A", "B"): 5, ("A", "C"): 6},("b", "a"): {("A", "C"): 7, ("A", "B"): 8},("b", "b"): {("A", "D"): 9, ("A", "B"): 10},}dataframe = pd.DataFrame(diccionario)dataframediccionario = {"b": 1, "a": 0, "c": 2}serie = pd.Series(diccionario)dataframe = pd.DataFrame(serie)dataframecalifornia_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")california_housing_traincalifornia_housing_train.head(n=10)california_housing_train.tail()california_housing_train.info()
<class 'pandas.core.frame.DataFrame'>RangeIndex: 17000 entries, 0 to 16999Data columns (total 9 columns):# Column Non-Null Count Dtype--- ------ -------------- -----0 longitude 17000 non-null float641 latitude 17000 non-null float642 housing_median_age 17000 non-null float643 total_rooms 17000 non-null float644 total_bedrooms 17000 non-null float645 population 17000 non-null float646 households 17000 non-null float647 median_income 17000 non-null float648 median_house_value 17000 non-null float64dtypes: float64(9)memory usage: 1.2 MB
4.4. Filas y columnas DataFrame
Se pueden obtener los índices y las columnas de un DataFrame
mediante los métodos index
y columns
diccionario = {"uno": pd.Series([1.0, 2.0, 3.0], index=["a", "b", "c"]),"dos": pd.Series([4.0, 5.0, 6.0, 7.0], index=["a", "b", "c", "d"])}dataframe = pd.DataFrame(diccionario)indices = dataframe.indexcolumnas = dataframe.columnsprint(f"El DataFrame tiene los índices {indices}\n")print(f"El DataFrame tiene las columnas {columnas}")
El DataFrame tiene los índicesIndex(['a', 'b', 'c', 'd'], dtype='object')El DataFrame tiene las columnasIndex(['uno', 'dos'], dtype='object')
4.5. Descripción del DataFrame
El método describe()
muestra un resumen estadístico rápido de los datos del DataFrame
california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")
california_housing_train.describe()
4.6. Ordenación del DataFrame
Se pueden ordenar alfabéticamente las filas de un DataFrame
mediante el método sort_index()
california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")
california_housing_train.sort_index().head()
Como en este caso las filas ya estaban ordenadas, establecemos ascending=False
para que el orden sea al revés
california_housing_train.sort_index(ascending=False).head()
Si lo que se quiere es ordenar las columnas hay que introducir axis=1
ya que por defecto es 0
california_housing_train.sort_index(axis=1).head()
Si lo que queremos es ordenar el DataFrame
a través de una columna determinada, tenemos que usar el método sort_values()
e indicarle la etiqueta de la columna sobre la que se quiere ordenar
california_housing_train.sort_values('median_house_value')
4.7. Estadísticas del DataFrame
Se pueden obtener estadísticas del DataFrame
, como la media, la moda, la desviación estándar
california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")california_housing_train.describe()california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")california_housing_train.sort_index().head()california_housing_train.sort_index(ascending=False).head()california_housing_train.sort_index(axis=1).head()california_housing_train.sort_values('median_house_value')california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")print(f"media:\n{california_housing_train.mean()}")print(f" desviación estandar:\n{california_housing_train.std()}")
media:longitude -119.562108latitude 35.625225housing_median_age 28.589353total_rooms 2643.664412total_bedrooms 539.410824population 1429.573941households 501.221941median_income 3.883578median_house_value 207300.912353dtype: float64desviación estandar:longitude 2.005166latitude 2.137340housing_median_age 12.586937total_rooms 2179.947071total_bedrooms 421.499452population 1147.852959households 384.520841median_income 1.908157median_house_value 115983.764387dtype: float64
Si se quieren obtener las estadísticas sobre las filas y no sobre las columnas hay que indicarlo mediante axis=1
california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")print(f"media:\n{california_housing_train.mean(axis=1)}")print(f" desviación estandar:\n{california_housing_train.std(axis=1)}")
media:0 8357.5970671 10131.5277782 9664.6423223 8435.0290784 7567.436111...16995 12806.40856716996 9276.77087816997 12049.50792216998 10082.05330016999 10863.022744Length: 17000, dtype: float64desviación estandar:0 22026.6124451 26352.9392722 28514.3165883 24366.7547474 21730.014569...16995 36979.67689916996 26158.00677116997 34342.87679216998 28408.15232916999 31407.119788Length: 17000, dtype: float64
Otra cosa útil que se puede obtener de los DataFrame
s es por ejemplo el número de veces que se repite cada ítem de una columna
california_housing_train["total_rooms"].value_counts()
1582.0 161527.0 151717.0 141471.0 141703.0 14..157.0 12760.0 1458.0 110239.0 14068.0 1Name: total_rooms, Length: 5533, dtype: int64
Por ejemplo, podemos ver que hay un total de 16 casas con 1582 habitaciones.
4.8. Memoria usada
Podemos ver la memoria que usa el dataframe
california_housing_train.memory_usage(deep=True)
Index 128longitude 136000latitude 136000housing_median_age 136000total_rooms 136000total_bedrooms 136000population 136000households 136000median_income 136000median_house_value 136000dtype: int64
5. Adición de datos
5.1. Adición de columnas
Se pueden añadir columnas fácilmente como operaciones de otras columnas
diccionario = {
"uno": pd.Series([1.0, 2.0, 3.0]),
"dos": pd.Series([4.0, 5.0, 6.0, 7.0])
}
dataframe = pd.DataFrame(diccionario)
dataframe["tres"] = dataframe["uno"] + dataframe["dos"]
dataframe["flag"] = dataframe["tres"] > 7.0
dataframe
También se pueden añadir columnas indicando qué valor tendrán todos sus ítems
dataframe["constante"] = 8.0
dataframe
Si se añade una Serie
que no tiene el mismo número de índices que el DataFrame
, esta se ajustará al número de índices del DataFrame
dataframe["Menos indices"] = dataframe["uno"][:2]
dataframe
Con los métodos anteriores la columna se añadía al final, pero si se quiere añadir la columna en una posición determinada se puede usar el método insert()
.
Por ejemplo, si se quiere añadir una columna en la posición 3 (teniendo en cuenta que se empieza a contar desde la posición 0), que el nombre de la columna sea columna insertada y que su valor sea el doble que el de la columna tres, se haría de la siguiente manera
dataframe.insert(loc=3, column="columna insertada", value=dataframe["tres"]*2)
dataframe
Si se quiere añadir más de una columna por comando se puede usar el método assign()
dataframe = dataframe.assign(
columna_asignada1 = dataframe["uno"] * dataframe["tres"],
columna_asignada2 = dataframe["dos"] * dataframe["tres"],
)
dataframe
5.2. Adición de filas
diccionario = {
"uno": pd.Series([1.0, 2.0, 3.0]),
"dos": pd.Series([4.0, 5.0, 6.0, 7.0])
}
dataframe = pd.DataFrame(diccionario)
dataframe.head()
Podemos añadir una fila al final con el método concat
(que veremos más en detalle después)
diccionario = {
"uno": [10.0],
"dos": [20.0]
}
dataframe = pd.concat([dataframe, pd.DataFrame(diccionario)])
dataframe
Vemos que se ha añadido la columna al final, pero que tiene el índice cero, así que reordenamos los índices mediante el método reset_index(drop=True)
dataframe = dataframe.reset_index(drop=True)
dataframe
6. Eliminación de datos
6.1. Eliminación de columnas
Se puede eliminar una columna determinada mediante el método pop()
dataframe.pop("constante")
dataframe
O mediante del
del dataframe["flag"]
dataframe
6.1. Eliminación de filas
diccionario = {
"uno": pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]),
"dos": pd.Series([11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0]),
"tres": pd.Series([21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0])
}
dataframe = pd.DataFrame(diccionario)
dataframe.head()
Si queremos eliminar una fila, podemos usar el método drop
, especificando su posición. Por ejemplo, si queremos eliminar la fila de la posición 1
dataframe = dataframe.drop(1)
dataframe
Si queremos eliminar la última fila
dataframe = dataframe.drop(len(dataframe)-1)
dataframe
Si lo que queremos es eliminar un rango de filas
dataframe = dataframe.drop(range(2, 5))
dataframe
Si lo que queremos es eliminar un conjunto de filas determinado
dataframe = dataframe.drop([5, 7, 9])
dataframe
Al igual que cuando añadimos filas, vemos que se han eliminado algunos índices, así que reordenamos los índices mediante el método reset_index(drop=True)
dataframe = dataframe.reset_index(drop=True)
dataframe
7. Operaciones sobre DataFrames
Se pueden realizar operaciones sobre los DataFrame
s al igual que se podía hacer con Numpy
diccionario = {
"uno": pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]),
"dos": pd.Series([11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0]),
"tres": pd.Series([21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0])
}
dataframe = pd.DataFrame(diccionario)
dataframe.head()
dataframe[ ["uno", "dos", "tres"] ] * 2
np.exp(dataframe[ ["uno", "dos", "tres"] ])
Si se quiere realizar operaciones más complejas se puede utilizar el método apply()
diccionario = {"uno": pd.Series([1.0, 2.0, 3.0]),"dos": pd.Series([4.0, 5.0, 6.0, 7.0])}dataframe = pd.DataFrame(diccionario)dataframe["tres"] = dataframe["uno"] + dataframe["dos"]dataframe["flag"] = dataframe["tres"] > 7.0dataframedataframe["constante"] = 8.0dataframedataframe["Menos indices"] = dataframe["uno"][:2]dataframedataframe.insert(loc=3, column="columna insertada", value=dataframe["tres"]*2)dataframedataframe = dataframe.assign(columna_asignada1 = dataframe["uno"] * dataframe["tres"],columna_asignada2 = dataframe["dos"] * dataframe["tres"],)dataframediccionario = {"uno": pd.Series([1.0, 2.0, 3.0]),"dos": pd.Series([4.0, 5.0, 6.0, 7.0])}dataframe = pd.DataFrame(diccionario)dataframe.head()diccionario = {"uno": [10.0],"dos": [20.0]}dataframe = pd.concat([dataframe, pd.DataFrame(diccionario)])dataframedataframe = dataframe.reset_index(drop=True)dataframedataframe.pop("constante")dataframedel dataframe["flag"]dataframediccionario = {"uno": pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]),"dos": pd.Series([11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0]),"tres": pd.Series([21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0])}dataframe = pd.DataFrame(diccionario)dataframe.head()dataframe = dataframe.drop(1)dataframedataframe = dataframe.drop(len(dataframe)-1)dataframedataframe = dataframe.drop(range(2, 5))dataframedataframe = dataframe.drop([5, 7, 9])dataframedataframe = dataframe.reset_index(drop=True)dataframediccionario = {"uno": pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]),"dos": pd.Series([11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0]),"tres": pd.Series([21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0])}dataframe = pd.DataFrame(diccionario)dataframe.head()dataframe[ ["uno", "dos", "tres"] ] * 2np.exp(dataframe[ ["uno", "dos", "tres"] ])dataframe = dataframe.apply(lambda x: x.max() - x.min())dataframe
uno 9.0dos 9.0tres 9.0dtype: float64
Se ha aplicado una función lambda
porque es una función sencilla, pero en caso de querer aplicar funciones más complejas, las podemos definir y aplicarlas
def funcion(x):if x < 10:return np.exp(x) - np.log(5*x) + np.sqrt(x)elif x < 20:return np.sin(x) + np.cos(x) + np.tan(x)else:return np.log(x) + np.log10(x) + np.log2(x)dataframe = dataframe.apply(funcion)dataframe
uno 8102.277265dos 8102.277265tres 8102.277265dtype: float64
Utilizar el método apply
en un dataframe es mucho más rápido que hacer un for
por cada una de las líneas y realizar la operación
california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")
california_housing_train.head()
Vamos a calcular el porcentaje de dormitorios del total de habitaciones
california_housing_train["percent_bedrooms"] = None
%time california_housing_train["percent_bedrooms"] = california_housing_train.apply(lambda x: x["total_bedrooms"] / x["total_rooms"], axis=1)
california_housing_train.head()
california_housing_train["percent_bedrooms"] = None
%time for i in range(len(california_housing_train)): california_housing_train["percent_bedrooms"][i] = california_housing_train["total_bedrooms"][i] / california_housing_train["total_rooms"][i]
california_housing_train.head()
Con la función lambda
ha tardado unos 300 ms, mientras que con el bucle for
ha tardado más de 1 segundo
8. Transpuesta
Se puede hacer la transpuesta de un DataFrame
mediante el método T
diccionario = {
"uno": pd.Series([1.0, 2.0, 3.0], index=["fila a", "fila b", "fila c"]),
"dos": pd.Series([4.0, 5.0, 6.0], index=["fila a", "fila b", "fila c"])
}
dataframe = pd.DataFrame(diccionario)
dataframe["tres"] = dataframe["uno"] + dataframe["dos"]
dataframe["flag"] = dataframe["tres"] > 7.0
dataframe.T
9. Conversión a Numpy
Si se quiere convertir una Serie
o DataFrame
a NumPy se puede usar el método to_numpy()
o usar la función np.asarray()
diccionario = {
"uno": pd.Series([1.0, 2.0, 3.0], index=["fila a", "fila b", "fila c"]),
"dos": pd.Series([4.0, 5.0, 6.0], index=["fila a", "fila b", "fila c"])
}
dataframe = pd.DataFrame(diccionario)
dataframe["tres"] = dataframe["uno"] + dataframe["dos"]
dataframe["flag"] = dataframe["tres"] > 7.0
dataframe
california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")california_housing_train.head()california_housing_train["percent_bedrooms"] = None%time california_housing_train["percent_bedrooms"] = california_housing_train.apply(lambda x: x["total_bedrooms"] / x["total_rooms"], axis=1)california_housing_train.head()california_housing_train["percent_bedrooms"] = None%time for i in range(len(california_housing_train)): california_housing_train["percent_bedrooms"][i] = california_housing_train["total_bedrooms"][i] / california_housing_train["total_rooms"][i]california_housing_train.head()diccionario = {"uno": pd.Series([1.0, 2.0, 3.0], index=["fila a", "fila b", "fila c"]),"dos": pd.Series([4.0, 5.0, 6.0], index=["fila a", "fila b", "fila c"])}dataframe = pd.DataFrame(diccionario)dataframe["tres"] = dataframe["uno"] + dataframe["dos"]dataframe["flag"] = dataframe["tres"] > 7.0dataframe.Tdiccionario = {"uno": pd.Series([1.0, 2.0, 3.0], index=["fila a", "fila b", "fila c"]),"dos": pd.Series([4.0, 5.0, 6.0], index=["fila a", "fila b", "fila c"])}dataframe = pd.DataFrame(diccionario)dataframe["tres"] = dataframe["uno"] + dataframe["dos"]dataframe["flag"] = dataframe["tres"] > 7.0dataframematriz_np = dataframe.to_numpy()matriz_np
CPU times: user 309 ms, sys: 86 µs, total: 309 msWall time: 309 ms/home/wallabot/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:1: SettingWithCopyWarning:A value is trying to be set on a copy of a slice from a DataFrameSee the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy"""Entry point for launching an IPython kernel.array([[1.0, 4.0, 5.0, False],[2.0, 5.0, 7.0, False],[3.0, 6.0, 9.0, True]], dtype=object)
matriz_np = np.asarray(dataframe)matriz_np
array([[1.0, 4.0, 5.0, False],[2.0, 5.0, 7.0, False],[3.0, 6.0, 9.0, True]], dtype=object)
Este ejemplo no es el más indicado, ya que mezcla números con booleanos, y como ya explicamos en el anterior post Cálculo matricial con NumPy, todos los elementos de un ndarray
tienen que ser del mismo tipo.
En este caso estamos mezclando números con booleanos, por lo que para solucionarlo NumPy los convierte todos a objetos
Para solucionar esto nos quedamos solo con los números y los convertimos a un ndarray
matriz_np = dataframe[ ["uno", "dos", "tres"] ].to_numpy()matriz_np, matriz_np.dtype
(array([[1., 4., 5.],[2., 5., 7.],[3., 6., 9.]]), dtype('float64'))
Ahora se puede ver que se ha creado un ndarray
donde todos los datos son de tipo float
10. Lectura de datos de fuentes externas
Una de las mayores fortalezas de Pandas es poder leer datos de archivos, por lo que no es necesario crearse un DataFrame
con los datos que se quieren procesar, sino que se pueden leer de un archivo
De la misma manera que se pueden crear DataFrame
s de archivos externos, también se pueden guardar DataFrame
s en archivos, para así crearte tu propio set de datos, configurarlo de la manera que quieras y guardarlo en un archivo para poder usarlo más adelante
En la siguiente tabla se muestran las funciones para leer y escribir archivos de distintos formatos
|Formato|Tipo de archivo|Función de lectura|Función de escritura||---|---|---|---| |texto|CSV|read_csv|to_csv| |texto|Fixed-Width Text File|read_fwf|| |texto|JSON|read_json|to_json| |texto|HTML|read_html|to_html| |texto|Local clipboard|read_clipboard|to_clipboard| |binary|MS Excel|read_excel|to_excel| |binary|OpenDocument|read_excel| |binary|HDF5 Format|read_hdf|to_hdf| |binary|Feather Format|read_feather|to_feather| |binary|Parquet Format|read_parquet|to_parquet| |binary|ORC Format|read_orc||binary|Msgpack|read_msgpack|to_msgpack| |binary|Stata|read_stata|to_stata| |binary|SAS|read_sas||binary|SPSS|read_spss||binary|Python Pickle Format|read_pickle|to_pickle| |SQL|SQL|read_sql|to_sql| |SQL|Google BigQuery|read_gbq|to_gbq|
11. Indexación en DataFrames
Hay muchas maneras de indexar en los DataFrame
s,
fechas = pd.date_range('1/1/2000', periods=8)
dataframe = pd.DataFrame(np.random.randn(8, 4), index=fechas, columns=['A', 'B', 'C', 'D'])
dataframe
11.1. Indexación de columnas
Para seleccionar columnas dentro de un DataFrame
lo podemos hacer seleccionando la columna entre corchetes []
, o indicando la columna como si fuese un método del DataFrame
fechas = pd.date_range('1/1/2000', periods=8)dataframe = pd.DataFrame(np.random.randn(8, 4), index=fechas, columns=['A', 'B', 'C', 'D'])dataframedataframe['A']
2000-01-01 0.8131532000-01-02 -0.2445842000-01-03 0.1257292000-01-04 0.3522752000-01-05 -2.0509762000-01-06 -0.3122962000-01-07 0.8978372000-01-08 0.271403Freq: D, Name: A, dtype: float64
dataframe.A
2000-01-01 0.8131532000-01-02 -0.2445842000-01-03 0.1257292000-01-04 0.3522752000-01-05 -2.0509762000-01-06 -0.3122962000-01-07 0.8978372000-01-08 0.271403Freq: D, Name: A, dtype: float64
Si se quieren unas filas determinadas se pasan mediante una lista
dataframe[ ['A', 'B'] ]
11.2. Indexación de filas por posiciones
Se puede seleccionar un rango de filas de un DataFrame
de la siguiente manera
dataframe[0:3]
Si solo se quiere seleccionar una sola fila, hay que indicar un rango de filas que incluya solo a esa, si por ejemplo se quiere seleccionar la fila número 1
dataframe[1:2]
Otro método para seleccionar una fila por su posición es el método iloc[]
dataframe.iloc[0:3]
Si se quieren unas filas determinadas, se pasa una lista con sus posiciones
dataframe.iloc[ [0, 2, 4] ]
11.3. Indexación de filas por etiquetas
Para seleccionar una fila por sus etiquetas podemos usar el método loc[]
dataframe[ ['A', 'B'] ]dataframe[0:3]dataframe[1:2]dataframe.iloc[0:3]dataframe.iloc[ [0, 2, 4] ]dataframe.loc['2000-01-01']
A 0.813153B -0.869356C 0.934293D 0.338644Name: 2000-01-01 00:00:00, dtype: float64
Si se quiere seleccionar un rango de filas, podemos indexarlas mediante los dos puntos :
dataframe.loc['2000-01-01':'2000-01-03']
Si se quieren unas filas determinadas se pasan mediante una lista
dataframe.loc[ ['2000-01-01', '2000-01-03', '2000-01-05'] ]
11.4. Selección de una porción del DataFrame mediante posiciones
dataframe.iloc[0:3, 0:2]
Si se quieren unas filas y columnas determinadas, se pasan listas con las posiciones deseadas
dataframe.iloc[ [0, 2, 4], [0, 2] ]
11.5. Selección de una porción del DataFrame mediante etiquetas
dataframe.loc['2000-01-01':'2000-01-03', 'A':'B']
Si se quieren unas filas y columnas determinadas, se pasan listas con las etiquetas deseadas
dataframe.loc[ ['2000-01-01', '2000-01-03', '2000-01-05'], ['A', 'C'] ]
11.6. Indexación por función lambda
Se pueden seleccionar datos de un DataFrame
que cumplan una condición dada por una función lambda
dataframe.loc[lambda dataframe:2*dataframe['A']+5*np.exp(dataframe['B'])>0.2]
Como se puede ver, esta forma de indexación es muy potente
11.7. Indexación condicional
Si no necesitamos funciones complejas para indexar, sino solo condicionales, podemos hacer
dataframe[dataframe['A']>0.2]
Podemos hacer múltiples condiciones
dataframe[(dataframe['A']>0.2) & (dataframe['B']>0.2)]
11.8. Indexación aleatoria
Mediante el método sample()
obtendremos una fila aleatoria del DataFrame
dataframe.sample()
Si queremos más de una muestra lo indicamos con el atributo n
dataframe.sample(n=3)
Si lo que se quiere son columnas aleatorias hay que indicarlo mediante axis=1
dataframe.sample(axis=1)
Si se quiere un único item del DataFrame
hay que llamar dos veces al método sample()
dataframe.sample(axis=1).sample()
12. Unión de DataFrames
12.1. Concatenación de DataFrames
Para concatenar varios DataFrame
s usamos el método concat()
, donde se le pasará una lista con los DataFrame
s que se quiere unir
dataframe.loc['2000-01-01':'2000-01-03']dataframe.loc[ ['2000-01-01', '2000-01-03', '2000-01-05'] ]dataframe.iloc[0:3, 0:2]dataframe.iloc[ [0, 2, 4], [0, 2] ]dataframe.loc['2000-01-01':'2000-01-03', 'A':'B']dataframe.loc[ ['2000-01-01', '2000-01-03', '2000-01-05'], ['A', 'C'] ]dataframe.loc[lambda dataframe:2*dataframe['A']+5*np.exp(dataframe['B'])>0.2]dataframe[dataframe['A']>0.2]dataframe[(dataframe['A']>0.2) & (dataframe['B']>0.2)]dataframe.sample()dataframe.sample(n=3)dataframe.sample(axis=1)dataframe.sample(axis=1).sample()dataframe1 = pd.DataFrame({"A": ["A0", "A1", "A2", "A3"],"B": ["B0", "B1", "B2", "B3"],"C": ["C0", "C1", "C2", "C3"],"D": ["D0", "D1", "D2", "D3"],})dataframe2 = pd.DataFrame({"A": ["A4", "A5", "A6", "A7"],"B": ["B4", "B5", "B6", "B7"],"C": ["C4", "C5", "C6", "C7"],"D": ["D4", "D5", "D6", "D7"],})dataframe3 = pd.DataFrame({"A": ["A8", "A9", "A10", "A11"],"B": ["B8", "B9", "B10", "B11"],"C": ["C8", "C9", "C10", "C11"],"D": ["D8", "D9", "D10", "D11"],})dataframe = pd.concat([dataframe1, dataframe2, dataframe3])print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe2}")print(f"dataframe3:\n{dataframe3}")print(f"\ndataframe:\n{dataframe}")
dataframe1:A B C D0 A0 B0 C0 D01 A1 B1 C1 D12 A2 B2 C2 D23 A3 B3 C3 D3dataframe2:A B C D0 A4 B4 C4 D41 A5 B5 C5 D52 A6 B6 C6 D63 A7 B7 C7 D7dataframe3:A B C D0 A8 B8 C8 D81 A9 B9 C9 D92 A10 B10 C10 D103 A11 B11 C11 D11dataframe:A B C D0 A0 B0 C0 D01 A1 B1 C1 D12 A2 B2 C2 D23 A3 B3 C3 D30 A4 B4 C4 D41 A5 B5 C5 D52 A6 B6 C6 D63 A7 B7 C7 D70 A8 B8 C8 D81 A9 B9 C9 D92 A10 B10 C10 D103 A11 B11 C11 D11
Como se puede ver, los índices 0
, 1
, 2
y 3
se repiten, porque cada dataframe tiene esos índices. Para que no ocurra esto hay que usar el parámetro ignore_index=True
dataframe = pd.concat([dataframe1, dataframe2, dataframe3], ignore_index=True)print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe2}")print(f"dataframe3:\n{dataframe3}")print(f"\ndataframe:\n{dataframe}")
dataframe1:A B C D0 A0 B0 C0 D01 A1 B1 C1 D12 A2 B2 C2 D23 A3 B3 C3 D3dataframe2:A B C D0 A4 B4 C4 D41 A5 B5 C5 D52 A6 B6 C6 D63 A7 B7 C7 D7dataframe3:A B C D0 A8 B8 C8 D81 A9 B9 C9 D92 A10 B10 C10 D103 A11 B11 C11 D11dataframe:A B C D0 A0 B0 C0 D01 A1 B1 C1 D12 A2 B2 C2 D23 A3 B3 C3 D34 A4 B4 C4 D45 A5 B5 C5 D56 A6 B6 C6 D67 A7 B7 C7 D78 A8 B8 C8 D89 A9 B9 C9 D910 A10 B10 C10 D1011 A11 B11 C11 D11
Si se hubiera querido hacer la concatenación a lo largo de las columnas habría que haber introducido la variable axis=1
dataframe = pd.concat([dataframe1, dataframe2, dataframe3], axis=1)print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe2}")print(f"dataframe3:\n{dataframe3}")print(f"\ndataframe:\n{dataframe}")
dataframe1:A B C D0 A0 B0 C0 D01 A1 B1 C1 D12 A2 B2 C2 D23 A3 B3 C3 D3dataframe2:A B C D0 A4 B4 C4 D41 A5 B5 C5 D52 A6 B6 C6 D63 A7 B7 C7 D7dataframe3:A B C D0 A8 B8 C8 D81 A9 B9 C9 D92 A10 B10 C10 D103 A11 B11 C11 D11dataframe:A B C D A B C D A B C D0 A0 B0 C0 D0 A4 B4 C4 D4 A8 B8 C8 D81 A1 B1 C1 D1 A5 B5 C5 D5 A9 B9 C9 D92 A2 B2 C2 D2 A6 B6 C6 D6 A10 B10 C10 D103 A3 B3 C3 D3 A7 B7 C7 D7 A11 B11 C11 D11
12.1.1. Intersección de concatenación
Hay dos maneras de hacer la concatenación, cogiendo todos los índices de los DataFrame
s o cogiendo solo los que coinciden, esto se determina mediante la variable join
, que admite los valores 'outer'
(por defecto) (coge todos los índices) o 'inner'
(solo los que coinciden)
Veamos un ejemplo de 'outer'
dataframe1 = pd.DataFrame({"A": ["A0", "A1", "A2", "A3"],"B": ["B0", "B1", "B2", "B3"],"C": ["C0", "C1", "C2", "C3"],"D": ["D0", "D1", "D2", "D3"],},index=[0, 1, 2, 3])dataframe4 = pd.DataFrame({"B": ["B2", "B3", "B6", "B7"],"D": ["D2", "D3", "D6", "D7"],"F": ["F2", "F3", "F6", "F7"],},index=[2, 3, 6, 7])dataframe = pd.concat([dataframe1, dataframe4], axis=1)print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe4}")print(f"\ndataframe:\n{dataframe}")
dataframe1:A B C D0 A0 B0 C0 D01 A1 B1 C1 D12 A2 B2 C2 D23 A3 B3 C3 D3dataframe2:B D F2 B2 D2 F23 B3 D3 F36 B6 D6 F67 B7 D7 F7dataframe:A B C D B D F0 A0 B0 C0 D0 NaN NaN NaN1 A1 B1 C1 D1 NaN NaN NaN2 A2 B2 C2 D2 B2 D2 F23 A3 B3 C3 D3 B3 D3 F36 NaN NaN NaN NaN B6 D6 F67 NaN NaN NaN NaN B7 D7 F7
Veamos un ejemplo de 'inner'
dataframe = pd.concat([dataframe1, dataframe4], axis=1, join="inner")print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe4}")print(f"\ndataframe:\n{dataframe}")
dataframe1:A B C D0 A0 B0 C0 D01 A1 B1 C1 D12 A2 B2 C2 D23 A3 B3 C3 D3dataframe2:B D F2 B2 D2 F23 B3 D3 F36 B6 D6 F67 B7 D7 F7dataframe:A B C D B D F2 A2 B2 C2 D2 B2 D2 F23 A3 B3 C3 D3 B3 D3 F3
12.2. Merge
de DataFrames
Antes hemos creado un dataframe nuevo con la unión de varios dataframes, ahora podemos completar un dataframe con otro, para ello usamos merge
, pasándole el parámetro on
, sobre qué columna queremos que se haga el merge
dataframe1 = pd.DataFrame({"Key": ["K0", "K1", "K2", "K3"],"A": ["A0", "A1", "A2", "A3"],"B": ["B0", "B1", "B2", "B3"],})dataframe2 = pd.DataFrame({"Key": ["K0", "K1", "K2", "K3"],"C": ["C0", "C1", "C2", "C3"],"D": ["D0", "D1", "D2", "D3"],})dataframe = dataframe1.merge(dataframe2, on="Key")print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe2}")print(f"\ndataframe:\n{dataframe}")
dataframe1:Key A B0 K0 A0 B01 K1 A1 B12 K2 A2 B23 K3 A3 B3dataframe2:Key C D0 K0 C0 D01 K1 C1 D12 K2 C2 D23 K3 C3 D3dataframe:Key A B C D0 K0 A0 B0 C0 D01 K1 A1 B1 C1 D12 K2 A2 B2 C2 D23 K3 A3 B3 C3 D3
En este caso los dos dataframes tenían una llave que se llamaba igual (Key
), pero en el caso de tener dataframes, en los que su llave se llame de otra forma podemos usar los parámetros left_on
y right_on
dataframe1 = pd.DataFrame({"Key1": ["K0", "K1", "K2", "K3"],"A": ["A0", "A1", "A2", "A3"],"B": ["B0", "B1", "B2", "B3"],})dataframe2 = pd.DataFrame({"Key2": ["K0", "K1", "K2", "K3"],"C": ["C0", "C1", "C2", "C3"],"D": ["D0", "D1", "D2", "D3"],})dataframe = dataframe1.merge(dataframe2, left_on="Key1", right_on="Key2")print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe2}")print(f"\ndataframe:\n{dataframe}")
dataframe1:Key1 A B0 K0 A0 B01 K1 A1 B12 K2 A2 B23 K3 A3 B3dataframe2:Key2 C D0 K0 C0 D01 K1 C1 D12 K2 C2 D23 K3 C3 D3dataframe:Key1 A B Key2 C D0 K0 A0 B0 K0 C0 D01 K1 A1 B1 K1 C1 D12 K2 A2 B2 K2 C2 D23 K3 A3 B3 K3 C3 D3
En el caso en el que una de las claves no coincida no se hará el merge
sobre esa clave
dataframe1 = pd.DataFrame({"Key1": ["K0", "K1", "K2", "K3"],"A": ["A0", "A1", "A2", "A3"],"B": ["B0", "B1", "B2", "B3"],})dataframe2 = pd.DataFrame({"Key2": ["K0", "K1", "K2", np.nan],"C": ["C0", "C1", "C2", "C3"],"D": ["D0", "D1", "D2", "D3"],})dataframe = dataframe1.merge(dataframe2, left_on="Key1", right_on="Key2")print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe2}")print(f"\ndataframe:\n{dataframe}")
dataframe1:Key1 A B0 K0 A0 B01 K1 A1 B12 K2 A2 B23 K3 A3 B3dataframe2:Key2 C D0 K0 C0 D01 K1 C1 D12 K2 C2 D23 NaN C3 D3dataframe:Key1 A B Key2 C D0 K0 A0 B0 K0 C0 D01 K1 A1 B1 K1 C1 D12 K2 A2 B2 K2 C2 D2
Para cambiar este comportamiento podemos usar el parámetro how
, que por defecto tiene el valor inner
, pero le podemos pasar el valor left
, right
y outer
dataframe1 = pd.DataFrame({"Key1": ["K0", "K1", "K2", "K3"],"A": ["A0", "A1", "A2", "A3"],"B": ["B0", "B1", "B2", "B3"],})dataframe2 = pd.DataFrame({"Key2": ["K0", "K1", "K2", np.nan],"C": ["C0", "C1", "C2", "C3"],"D": ["D0", "D1", "D2", "D3"],})dataframe_inner = dataframe1.merge(dataframe2, left_on="Key1", right_on="Key2", how="inner")dataframe_left = dataframe1.merge(dataframe2, left_on="Key1", right_on="Key2", how="left")dataframe_right = dataframe1.merge(dataframe2, left_on="Key1", right_on="Key2", how="right")dataframe_outer = dataframe1.merge(dataframe2, left_on="Key1", right_on="Key2", how="outer")print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe2}")print(f"\ndataframe inner:\n{dataframe_inner}")print(f"\ndataframe left:\n{dataframe_left}")print(f"\ndataframe right:\n{dataframe_right}")print(f"\ndataframe outer:\n{dataframe_outer}")
dataframe1:Key1 A B0 K0 A0 B01 K1 A1 B12 K2 A2 B23 K3 A3 B3dataframe2:Key2 C D0 K0 C0 D01 K1 C1 D12 K2 C2 D23 NaN C3 D3dataframe inner:Key1 A B Key2 C D0 K0 A0 B0 K0 C0 D01 K1 A1 B1 K1 C1 D12 K2 A2 B2 K2 C2 D2dataframe left:Key1 A B Key2 C D0 K0 A0 B0 K0 C0 D01 K1 A1 B1 K1 C1 D12 K2 A2 B2 K2 C2 D23 K3 A3 B3 NaN NaN NaNdataframe right:Key1 A B Key2 C D0 K0 A0 B0 K0 C0 D01 K1 A1 B1 K1 C1 D12 K2 A2 B2 K2 C2 D23 NaN NaN NaN NaN C3 D3dataframe outer:Key1 A B Key2 C D0 K0 A0 B0 K0 C0 D01 K1 A1 B1 K1 C1 D12 K2 A2 B2 K2 C2 D23 K3 A3 B3 NaN NaN NaN4 NaN NaN NaN NaN C3 D3
Como se puede ver, cuando se elige left
solo se añaden los valores del dataframe de la izquierda y cuando se elige right
, los valores del dataframe de la derecha
12.3. Join
de dataframes
La última herramienta de unión de dataframes es join
. Es similar a merge
, solo que en vez de buscar similitudes en función de columnas especificadas, las busca en función de los índices.
dataframe1 = pd.DataFrame({"A": ["A0", "A1", "A2", "A3"],"B": ["B0", "B1", "B2", "B3"],},index=["K0", "K1", "K2", "K3"])dataframe2 = pd.DataFrame({"C": ["C0", "C1", "C2", "C3"],"D": ["D0", "D1", "D2", "D3"],},index=["K0", "K1", "K2", "K3"])dataframe = dataframe1.join(dataframe2)print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe2}")print(f"\ndataframe:\n{dataframe}")
dataframe1:A BK0 A0 B0K1 A1 B1K2 A2 B2K3 A3 B3dataframe2:C DK0 C0 D0K1 C1 D1K2 C2 D2K3 C3 D3dataframe:A B C DK0 A0 B0 C0 D0K1 A1 B1 C1 D1K2 A2 B2 C2 D2K3 A3 B3 C3 D3
En este caso, los índices son iguales, pero cuando son distintos podemos especificar la manera de unir los dataframes mediante el parámetro how
, que por defecto tiene el valor inner
, pero puede tener el valor left
, right
y outer
dataframe1 = pd.DataFrame({"A": ["A0", "A1", "A2", "A3"],"B": ["B0", "B1", "B2", "B3"],},index=["K0", "K1", "K2", "K3"])dataframe2 = pd.DataFrame({"C": ["C0", "C2", "C3", "C4"],"D": ["D0", "D2", "D3", "D4"],},index=["K0", "K2", "K3", "K4"])dataframe_inner = dataframe1.join(dataframe2, how="inner")dataframe_left = dataframe1.join(dataframe2, how="left")dataframe_right = dataframe1.join(dataframe2, how="right")dataframe_outer = dataframe1.join(dataframe2, how="outer")print(f"dataframe1:\n{dataframe1}")print(f"dataframe2:\n{dataframe2}")print(f"\ndataframe inner:\n{dataframe_inner}")print(f"\ndataframe left:\n{dataframe_left}")print(f"\ndataframe rigth:\n{dataframe_right}")print(f"\ndataframe outer:\n{dataframe_outer}")
dataframe1:A BK0 A0 B0K1 A1 B1K2 A2 B2K3 A3 B3dataframe2:C DK0 C0 D0K2 C2 D2K3 C3 D3K4 C4 D4dataframe:A B C DK0 A0 B0 C0 D0K2 A2 B2 C2 D2K3 A3 B3 C3 D3dataframe:A B C DK0 A0 B0 C0 D0K1 A1 B1 NaN NaNK2 A2 B2 C2 D2K3 A3 B3 C3 D3dataframe:A B C DK0 A0 B0 C0 D0K2 A2 B2 C2 D2K3 A3 B3 C3 D3K4 NaN NaN C4 D4dataframe:A B C DK0 A0 B0 C0 D0K1 A1 B1 NaN NaNK2 A2 B2 C2 D2K3 A3 B3 C3 D3K4 NaN NaN C4 D4
13. Datos faltantes (NaN
)
En un DataFrame
puede haber algunos datos faltantes, Pandas los representa como np.nan
diccionario = {
"uno": pd.Series([1.0, 2.0, 3.0]),
"dos": pd.Series([4.0, 5.0, 6.0, 7.0])
}
dataframe = pd.DataFrame(diccionario)
dataframe
13.1. Eliminación de las filas con datos faltantes
Para no tener filas con datos faltantes, se pueden eliminar estas
dataframe.dropna(how="any")
13.2. Eliminación de las columnas con datos faltantes
dataframe.dropna(axis=1, how='any')
13.3. Máscara booleana con las posiciones faltantes
pd.isna(dataframe)
13.4. Rellenado de los datos faltantes
dataframe.fillna(value=5.5, inplace=True)
dataframe
Tip: Poniendo la variable
inplace=True
se modifica elDataFrame
sobre el que se está operando, así no hace falta escribirdataframe = dataframe.fillna(value=5.5)
14. Series temporales
Pandas ofrece la posibilidad de trabajar con series temporales. Por ejemplo, creamos una Serie
de 100 datos aleatorios cada segundo desde el 01/01/2021
diccionario = {"uno": pd.Series([1.0, 2.0, 3.0]),"dos": pd.Series([4.0, 5.0, 6.0, 7.0])}dataframe = pd.DataFrame(diccionario)dataframedataframe.dropna(how="any")dataframe.dropna(axis=1, how='any')pd.isna(dataframe)dataframe.fillna(value=5.5, inplace=True)dataframeindices = pd.date_range("1/1/2021", periods=100, freq="S")datos = np.random.randint(0, 500, len(indices))serie_temporal = pd.Series(datos, index=indices)serie_temporal
2021-01-01 00:00:00 2412021-01-01 00:00:01 142021-01-01 00:00:02 1902021-01-01 00:00:03 4072021-01-01 00:00:04 94...2021-01-01 00:01:35 2752021-01-01 00:01:36 562021-01-01 00:01:37 4482021-01-01 00:01:38 1512021-01-01 00:01:39 316Freq: S, Length: 100, dtype: int64
Esta funcionalidad de Pandas es muy potente, por ejemplo, podemos tener un conjunto de datos en unas horas determinadas de un huso horario y cambiarlas a otro huso
horas = pd.date_range("3/6/2021 00:00", periods=10, freq="H")datos = np.random.randn(len(horas))serie_horaria = pd.Series(datos, horas)serie_horaria
2021-03-06 00:00:00 -0.8535242021-03-06 01:00:00 -1.3553722021-03-06 02:00:00 -1.2675032021-03-06 03:00:00 -1.1557872021-03-06 04:00:00 0.7309352021-03-06 05:00:00 1.4359572021-03-06 06:00:00 0.4609122021-03-06 07:00:00 0.7234512021-03-06 08:00:00 -0.8533372021-03-06 09:00:00 0.456359Freq: H, dtype: float64
Localizamos los datos en un huso horario
serie_horaria_utc = serie_horaria.tz_localize("UTC")serie_horaria_utc
2021-03-06 00:00:00+00:00 -0.8535242021-03-06 01:00:00+00:00 -1.3553722021-03-06 02:00:00+00:00 -1.2675032021-03-06 03:00:00+00:00 -1.1557872021-03-06 04:00:00+00:00 0.7309352021-03-06 05:00:00+00:00 1.4359572021-03-06 06:00:00+00:00 0.4609122021-03-06 07:00:00+00:00 0.7234512021-03-06 08:00:00+00:00 -0.8533372021-03-06 09:00:00+00:00 0.456359Freq: H, dtype: float64
Y ahora las podemos cambiar a otro uso
serie_horaria_US = serie_horaria_utc.tz_convert("US/Eastern")serie_horaria_US
2021-03-05 19:00:00-05:00 -0.8535242021-03-05 20:00:00-05:00 -1.3553722021-03-05 21:00:00-05:00 -1.2675032021-03-05 22:00:00-05:00 -1.1557872021-03-05 23:00:00-05:00 0.7309352021-03-06 00:00:00-05:00 1.4359572021-03-06 01:00:00-05:00 0.4609122021-03-06 02:00:00-05:00 0.7234512021-03-06 03:00:00-05:00 -0.8533372021-03-06 04:00:00-05:00 0.456359Freq: H, dtype: float64
15. Datos categóricos
Pandas ofrece la posibilidad de añadir datos categóricos en un DataFrame
. Supongamos el siguiente DataFrame
dataframe = pd.DataFrame(
{"id": [1, 2, 3, 4, 5, 6], "raw_grade": ["a", "b", "b", "a", "a", "e"]}
)
dataframe
Podemos convertir los datos de la columna raw_grade
a datos categóricos mediante el método astype()
dataframe['grade'] = dataframe["raw_grade"].astype("category")
dataframe
Las columnas raw_grade
y grade
parecen iguales, pero si vemos la información del DataFrame
podemos ver que no es así
dataframe = pd.DataFrame({"id": [1, 2, 3, 4, 5, 6], "raw_grade": ["a", "b", "b", "a", "a", "e"]})dataframedataframe['grade'] = dataframe["raw_grade"].astype("category")dataframedataframe.info()
<class 'pandas.core.frame.DataFrame'>RangeIndex: 6 entries, 0 to 5Data columns (total 3 columns):# Column Non-Null Count Dtype--- ------ -------------- -----0 id 6 non-null int641 raw_grade 6 non-null object2 grade 6 non-null categorydtypes: category(1), int64(1), object(1)memory usage: 334.0+ bytes
Se puede ver que la columna grade
es de tipo categórico
Podemos ver las categorías de los tipos de datos categóricos mediante el método cat.categories()
dataframe["grade"].cat.categories
Index(['a', 'b', 'e'], dtype='object')
Podemos también renombrar las categorías con el mismo método, pero introduciendo una lista con las nuevas categorías.
dataframe["grade"].cat.categories = ["very good", "good", "very bad"]
dataframe
Pandas nos da la posibilidad de codificar numéricamente los datos categóricos mediante el método get_dummies
pd.get_dummies(dataframe["grade"])
16. Groupby
Podemos agrupar los dataframes por valores de alguna de las columnas. Volvamos a cargar el dataframe con el valor de las casas de California.
california_housing_train = pd.read_csv("https://raw.githubusercontent.com/maximofn/portafolio/main/posts/california_housing_train.csv")
california_housing_train.head()
Ahora podemos agrupar los datos por alguna de las columnas, por ejemplo, agrupemos las casas en función del número de años y veamos cuántas casas hay de cada edad con count
california_housing_train.groupby("housing_median_age").count().head()
Como vemos en todas las columnas, obtenemos el mismo valor, que es el número de casas que hay con una determinada edad, pero podemos saber la media del valor de cada columna con mean
california_housing_train.groupby("housing_median_age").mean().head()
Podemos obtener varias medidas de cada edad mediante el comando agg
(aggregation), pasándole las medidas que queremos mediante una lista, por ejemplo veamos el mínimo, el máximo y la media de cada columna para cada edad de casa
california_housing_train.groupby("housing_median_age").agg(['min', 'max', 'mean']).head()
Podemos especificar sobre qué columnas queremos realizar ciertos cálculos mediante el paso de un diccionario, donde las claves serán las columnas sobre las que queremos realizar cálculos y los valores serán listas con los cálculos
california_housing_train.groupby("housing_median_age").agg({'total_rooms': ['min', 'max', 'mean'], 'total_bedrooms': ['min', 'max', 'mean', 'median']}).head()
Podemos agrupar por más de una columna, para ello, hay que pasar las columnas en una lista
california_housing_train.groupby(["housing_median_age", "total_bedrooms"]).mean()
17. Gráficos
Pandas ofrece la posibilidad de representar los datos de nuestros DataFrame
s en gráficos para poder obtener una mejor representación de ello. Para ello hace uso de la librería matplotlib
que veremos en el siguiente post
17.1. Gráfica básica
Para representar los datos en una gráfica, la manera más fácil es usar el método plot()
serie = pd.Series(np.random.randn(1000), index=pd.date_range("1/1/2000", periods=1000))
serie = serie.cumsum()
serie.plot()
En el caso de tener un DataFrame
, el método plot()
representará cada una de las columnas del DataFrame
dataframe = pd.DataFrame(
np.random.randn(1000, 4), index=ts.index, columns=["A", "B", "C", "D"]
)
dataframe = dataframe.cumsum()
dataframe.plot()
17.2. Diagrama de barras verticales
Hay más métodos de crear gráficos, como el diagrama de barras vertical mediante plot.bar()
dataframe = pd.DataFrame(np.random.rand(10, 4), columns=["a", "b", "c", "d"])
dataframe.plot.bar()
Si queremos apilar las barras, lo indicamos mediante la variable stacked=True
dataframe.plot.bar(stacked=True)
17.3. Diagrama de barras horizontal
Para crear un diagrama de barras horizontal usamos plot.barh()
dataframe.plot.barh()
Si queremos apilar las barras, lo indicamos mediante la variable stacked=True
dataframe.plot.barh(stacked=True)
17.4. Histograma
Para crear un histograma usamos plot.hist()
dataframe = pd.DataFrame(
{
"a": np.random.randn(1000) + 1,
"b": np.random.randn(1000),
"c": np.random.randn(1000) - 1,
}
)
dataframe.plot.hist(alpha=0.5)
Si queremos apilar las barras, lo indicamos mediante la variable stacked=True
dataframe.plot.hist(alpha=0.5, stacked=True)
Si queremos añadir más columnas, es decir, si queremos que el histograma sea más informativo o preciso, lo indicamos mediante la variable bins
dataframe.plot.hist(alpha=0.5, stacked=True, bins=20)
17.5. Diagramas de velas
Para crear un diagrama de velas usamos plot.box()
dataframe = pd.DataFrame(np.random.rand(10, 5), columns=["A", "B", "C", "D", "E"])
dataframe.plot.box()
17.6. Gráficos de áreas
Para crear un gráfico de áreas usamos plot.area()
dataframe.plot.area()
17.7. Diagrama de dispersión
Para crear un diagrama de dispersión usamos plot.scatter()
, donde hay que indicar las variables x
y y
del diagrama
dataframe.plot.scatter(x='A', y='B')
17.8. Gráfico de contenedor hexágonal
Para crear un gráfico de contenedor hexagonal usamos plot.hexbin()
, donde hay que indicar las variables x
y y
del diagrama y el tamaño de la malla mediante gridsize
dataframe = pd.DataFrame(np.random.randn(1000, 2), columns=["a", "b"])
dataframe["b"] = dataframe["b"] + np.arange(1000)
dataframe.plot.hexbin(x="a", y="b", gridsize=25)