Entrada

Resumen del curso 'Pandas' de Kaggle (2) - Lecciones 4–6

Resumen práctico de pandas para limpiar y transformar datos. Sintetiza y amplía el curso abierto de Kaggle; esta segunda parte cubre las lecciones 4–6.

Resumen del curso 'Pandas' de Kaggle (2) - Lecciones 4–6

Recojo aquí lo estudiado a través del curso de Pandas de Kaggle.
Como la extensión es considerable, lo separé en 2 partes.

Certificado de finalización

Lección 4. Agrupar y ordenar

A veces necesitamos clasificar los datos, aplicar operaciones por grupo o reordenarlos según un criterio.

Análisis por grupos

Con el método groupby() puedes agrupar las filas cuyo valor en una columna sea igual y, después, obtener resúmenes u operar por grupo.

Antes vimos el método value_counts(); lo mismo puede implementarse con groupby() así:

1
reviews.groupby('taster_name').size()
  1. Agrupa el DataFrame reviews por filas con el mismo valor en la columna taster_name
  2. Devuelve como Series el tamaño de cada grupo (número de filas incluidas)

O bien:

1
reviews.groupby('taster_name').taster_name.count()
  1. Agrupa el DataFrame reviews por filas con el mismo valor en la columna taster_name
  2. En cada grupo, selecciona la columna taster_name
  3. Devuelve como Series el número de valores no nulos

Es decir, value_counts() es un atajo de operaciones como las anteriores. Además de count(), puedes usar cualquier función de resumen de forma análoga. Por ejemplo, para ver el precio mínimo por puntuación:

1
reviews.groupby('points').price.min()
1
2
3
4
5
6
7
points
80      5.0
81      5.0
       ... 
99     44.0
100    80.0
Name: price, Length: 21, dtype: float64
  1. Agrupa reviews por valores iguales en la columna points
  2. Selecciona la columna price en cada grupo
  3. Devuelve como Series el mínimo de cada grupo

También puedes agrupar por más de una columna. Para seleccionar, por país y provincia, el vino con la puntuación máxima:

1
reviews.groupby(['country', 'province']).apply(lambda df: df.loc[df.points.idxmax()])

Otro método útil de los objetos DataFrameGroupBy es agg(). Permite ejecutar múltiples funciones a la vez sobre cada grupo.

Como argumento puedes pasar:

  • una función
  • una cadena con el nombre de una función
  • una lista de funciones o de nombres de función
  • un diccionario que mapea etiquetas de ejes a función o lista de funciones a aplicar sobre ese eje

Y dichas funciones deben:

Esta aclaración no aparece en el curso original de Kaggle; la he ampliado consultando la documentación oficial de pandas.

Por ejemplo, para obtener estadísticas de precio por país:

1
reviews.groupby(['country']).price.agg([len, min, max])

Aquí len es la función incorporada de Python len(). En este ejemplo la usamos para imprimir el número de datos de precio (price) por cada grupo (country), incluyendo valores nulos. Como acepta DataFrame o Series como entrada, puede usarse así.

El método count() de pandas, en cambio, devuelve solo el número de valores no nulos, por lo que su comportamiento difiere.

Esta aclaración no aparece en el curso original de Kaggle; la he ampliado consultando la documentación oficial de Python y pandas.

Índice múltiple

Al agrupar y analizar con groupby(), a veces obtendrás un DataFrame con índice de múltiples niveles en lugar de etiquetas simples.

1
2
countries_reviewed = reviews.groupby(['country', 'province']).description.agg([len])
countries_reviewed
len
Countryprovince
ArgentinaMendoza Province3264
Other536
.........
UruguaySan Jose3
Uruguay24
1
2
mi = countries_reviewed.index
type(mi)
1
pandas.core.indexes.multi.MultiIndex

El índice múltiple añade métodos para tratar jerarquías que no existen en el índice simple. Puedes ver ejemplos y pautas en la sección MultiIndex / advanced indexing de la guía de usuario de pandas.

El método que más usarás probablemente será reset_index() para volver a un índice normal:

1
countries_reviewed.reset_index()
 countryprovincelen
0ArgentinaMendoza Province3264
1ArgentinaOther536
423UruguaySan Jose3
424UruguayUruguay24

Ordenación

Si observas countries_reviewed, verás que el resultado del agrupado vuelve ordenado por el valor del índice. Es decir, las filas del resultado de groupby se ordenan por los valores del índice, no por el contenido.

Puedes ordenar manualmente según convenga con sort_values(). Por ejemplo, para ordenar país y provincia por el número de filas (‘len’) en orden ascendente:

1
2
countries_reviewed = countries_reviewed.reset_index()
countries_reviewed.sort_values(by='len')
 countryprovincelen
179GreeceMuscat of Kefallonian1
192GreeceSterea Ellada1
415USWashington8639
392USCalifornia36247

sort_values() ordena de forma ascendente por defecto; con la opción siguiente puedes ordenar en descendente:

1
countries_reviewed.sort_values(by='len', ascending=False)
 countryprovincelen
392USCalifornia36247
415USWashington8639
63ChileCoelemu1
149GreeceBeotia1

Para ordenar por índice, usa sort_index(). Tiene los mismos argumentos y el mismo orden por defecto (descendente) que sort_values().

1
countries_reviewed.sort_index()
 countryprovincelen
0ArgentinaMendoza Province3264
1ArgentinaOther536
423UruguaySan Jose3
424UruguayUruguay24

Por último, puedes ordenar por varias columnas a la vez:

1
countries_reviewed.sort_values(by=['country', 'len'])

Lección 5. Tipos de datos y valores faltantes

En la práctica, los datos rara vez vienen perfectamente limpios; a menudo hay que convertir tipos o tratar valores ausentes. En la preparación y el análisis, esta fase suele ser el mayor escollo.

Tipos de datos

El tipo de una columna de un DataFrame o de una Series se llama dtype. Con el atributo dtype puedes ver el tipo de una columna. Por ejemplo, para ver el dtype de price en reviews:

1
reviews.price.dtype
1
dtype('float64')

Con el atributo dtypes ves todos los tipos de las columnas a la vez:

1
reviews.dtypes
1
2
3
4
5
6
country        object
description    object
                ...  
variety        object
winery         object
Length: 13, dtype: object

El tipo indica cómo almacena internamente pandas los datos. Por ejemplo, float64 es coma flotante de 64 bits; int64, entero de 64 bits.

Un detalle: las columnas de cadenas no tienen tipo propio; se consideran objetos (object).

Con astype() puedes convertir una columna de un tipo a otro. Por ejemplo, convertir points (antes int64) a float64:

1
reviews.points.astype('float64')
1
2
3
4
5
6
0         87.0
1         87.0
          ... 
129969    90.0
129970    90.0
Name: points, Length: 129971, dtype: float64

El índice de un DataFrame o de una Series también tiene tipo:

1
reviews.index.dtype
1
dtype('int64')

Además, pandas soporta otros tipos como categórico o series temporales.

Valores faltantes

Las entradas vacías o sin valor reciben NaN (de “Not a Number”). Por razones técnicas, NaN es siempre de tipo float64.

Pandas ofrece funciones específicas para tratar valores faltantes. Ya vimos algo parecido: además de métodos, existen las funciones independientes pd.isna y pd.notna. Indican con booleanos si una entrada es (o no) faltante, y se pueden usar así:

1
reviews[pd.isna(reviews.country)]

A menudo conviene detectar valores faltantes y rellenarlos adecuadamente. Con fillna() puedes reemplazarlos por un valor conveniente. Por ejemplo, para sustituir todos los NaN de region_2 por "Unknown":

1
reviews.region_2.fillna("Unknown")

También puedes usar las estrategias forward fill y backward fill, que rellenan con el valor válido más cercano anterior o posterior, con ffill() y bfill(), respectivamente.

Antes podía usarse fillna() con el argumento method='ffill' o 'bfill', pero desde pandas 2.1.0 ese uso está en desuso (deprecated). Se recomienda usar ffill() o bfill() según corresponda.

A veces, aunque no haya faltantes, hay que reemplazar sistemáticamente unos valores por otros. En el curso original de Kaggle se da el ejemplo de cambiar el handle de Twitter de un revisor; buen ejemplo, pero para un caso más cercano: supongamos que Corea del Sur crea una nueva división administrativa al norte de Gyeonggi, 경기북도, y tenemos un dataset con ese nombre oficial. Ahora imaginemos que alguien propone, y logra imponer, renombrarlo como 평화누리특별자치도. Es hipotético, pero daba miedo que algo parecido pudiera ocurrir de verdad. Para reflejarlo en el dataset habría que cambiar "Gyeonggibuk-do" por "Pyeonghwanuri State" o "Pyeonghwanuri Special Self-Governing Province". Una forma de hacerlo en pandas es con replace():

1
rok_2030_census.province.replace("Gyeonggibuk-do", "Pyeonghwanuri Special Self-Governing Province")

Con este código, en la columna province de rok_2030_census todas las instancias de "Gyeonggibuk-do" se reemplazan eficazmente por ‘el nombre largo’. Alivia saber que nadie tuvo que ejecutar este cambio en la vida real.

Estos reemplazos de texto también son útiles al limpiar datos y tratar faltantes, ya que a menudo los valores ausentes aparecen como cadenas como "Unknown", "Undisclosed" o "Invalid" en lugar de NaN. En datasets generados con OCR de documentación antigua, esto es incluso lo más habitual.

Lección 6. Renombrar y combinar

A veces hay que cambiar nombres de columnas o del índice, y a menudo hay que combinar varios DataFrames o Series.

Cambiar nombres

Con rename() puedes renombrar columnas o índices. Acepta varios formatos de entrada, pero lo más cómodo suele ser un diccionario de Python. Por ejemplo, para cambiar la columna points a score y renombrar los índices 0, 1 a firstEntry, secondEntry:

1
reviews.rename(columns={'points': 'score'})
1
reviews.rename(index={0: 'firstEntry', 1: 'secondEntry'})

En realidad, es más común renombrar columnas que valores del índice; y para lo segundo suele ser más práctico usar set_index() como vimos antes.

Las etiquetas del eje de filas y el de columnas tienen su propia propiedad name, y con rename_axis() puedes renombrar estos ejes. Por ejemplo, nombrar el eje de filas como wines y el de columnas como fields:

1
reviews.rename_axis("wines", axis='index').rename_axis("fields", axis='columns')

Combinar datasets

A veces hay que unir DataFrames entre sí, o Series entre sí. Para ello, pandas ofrece tres funciones clave, de más simple a más compleja: concat(), join() y merge(). El curso de Kaggle señala que la mayoría de cosas que haces con merge() pueden hacerse de forma más sencilla con join(), por lo que se centra en las dos primeras.

concat() es la más simple: concatena varios DataFrames o Series a lo largo de un eje. Es útil cuando los objetos a unir comparten los mismos campos (columnas). Por defecto concatena a lo largo del eje de filas; con axis=1 o axis='columns' lo hace por columnas.

1
2
3
4
5
6
7
8
>>> s1 = pd.Series(['a', 'b'])
>>> s2 = pd.Series(['c', 'd'])
>>> pd.concat([s1, s2])
0    a
1    b
0    c
1    d
dtype: object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> df1 = pd.DataFrame([['a', 1], ['b', 2]],
...                    columns=['letter', 'number'])
>>> df1
  letter  number
0      a       1
1      b       2
>>> df2 = pd.DataFrame([['c', 3], ['d', 4]],
...                    columns=['letter', 'number'])
>>> df2
  letter  number
0      c       3
1      d       4
>>> pd.concat([df1, df2])
  letter  number
0      a       1
1      b       2
0      c       3
1      d       4
>>> df4 = pd.DataFrame([['bird', 'polly'], ['monkey', 'george']],
...                    columns=['animal', 'name'])
>>> df4
   animal    name
0    bird   polly
1  monkey  george
>>> pd.concat([df1, df4], axis=1)
  letter  number  animal    name
0      a       1    bird   polly
1      b       2  monkey  george

Según la documentación oficial de pandas, si tienes que unir muchas filas en un solo DataFrame, no es recomendable añadirlas una a una dentro de un bucle; es mejor construir una lista con todas y concatenarlas de una sola vez con concat().

join() es algo más complejo: añade a un DataFrame otro DataFrame alineando por el índice. Si hay nombres de columnas duplicados, especifica sufijos con lsuffix y rsuffix para distinguirlas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>>> df = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3', 'K4', 'K5'],
...                    'A': ['A0', 'A1', 'A2', 'A3', 'A4', 'A5']})
>>> df
  key   A
0  K0  A0
1  K1  A1
2  K2  A2
3  K3  A3
4  K4  A4
5  K5  A5
>>> other = pd.DataFrame({'key': ['K0', 'K1', 'K2'],
...                       'B': ['B0', 'B1', 'B2']})
>>> other
  key   B
0  K0  B0
1  K1  B1
2  K2  B2
>>> df.join(other, lsuffix='_caller', rsuffix='_other')
  key_caller   A key_other    B
0         K0  A0        K0   B0
1         K1  A1        K1   B1
2         K2  A2        K2   B2
3         K3  A3       NaN  NaN
4         K4  A4       NaN  NaN
5         K5  A5       NaN  NaN
Esta entrada está licenciada bajo CC BY-NC 4.0 por el autor.