Saltar al contenido principal

Groupby, split-apply-combine y pandas

En este tutorial, aprenderás a usar la operación groupby de pandas, basada en la conocida estrategia split-apply-combine, con datos de películas de Netflix.
· 8 min leer

Explora con IA

Netflix publicó recientemente algunos datos de valoraciones de usuarios. Yo quería responder a una pregunta sencilla: ¿los suscriptores de Netflix prefieren las películas antiguas o las nuevas?

De forma intuitiva, querrás dividir el conjunto de datos en grupos, uno por cada año, calcular una estadística resumen, como la media o la mediana, y comprobar si esa estadística aumenta con los años (después, quizá quieras hacer una prueba estadística).

Lo mejor es que existe un marco conceptual para hacer y pensar en esto, con implementaciones tanto en Python como en R. El enfoque se conoce como "split-apply-combine" porque...

  • Paso 1: divides los datos en grupos creando un objeto groupby a partir del DataFrame original;
  • Paso 2: aplicas una función, en este caso, una función de agregación que calcula una estadística resumen (en este paso también puedes transformar o filtrar tus datos);
  • Paso 3: combinas los resultados en un nuevo DataFrame.

Este es el marco conceptual para el análisis que nos ocupa. En este post aprenderás a hacerlo para responder a la pregunta sobre las valoraciones de Netflix usando el paquete de Python pandas. Podrías hacer lo mismo en R usando, por ejemplo, el paquete dplyr. También voy a profundizar necesariamente en los objetos groupby, que no son los más intuitivos. El proceso split-apply-combine con objetos groupby es un patrón que todos aplicamos de forma intuitiva, como veremos, pero fue Hadley Wickham quien formalizó el procedimiento en 2011 con su artículo The Split-Apply-Combine Strategy for Data Analysis.

Si te resulta útil esta técnica, puedes aprender más (entre muchas otras cosas) y practicarla en nuestro curso Manipulating DataFrames with pandas.

Exploración de datos con pandas

Importa tus datos

Aquí usarás pandas, objetos groupby y los principios de split-apply-combine para ver cómo varían las valoraciones de películas de Netflix en función del año de estreno. Encontré los datos originalmente en data.world aquí. Puedes encontrar todo el código de este post aquí si quieres reproducirlo.

Primero importarás los paquetes necesarios y los datos, y echarás un vistazo a las cinco primeras filas:

# Import packages and set visualization style
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
%matplotlib inline
# Import data and check out head of DataFrame
df = pd.read_csv('data/chasewillden-netflix-shows/data/netflix.csv')
df.head()
 titleratingratinglevelratingdescriptionrelease_yearuser_rating_scoreuser_rating_size
0White ChicksPG-13crude and sexual humor, language and some drug...80200482.080
1Lucky Number SlevinRstrong violence, sexual content and adult lang...1002006NaN82
2Grey's AnatomyTV-14Parents strongly cautioned. May be unsuitable ...90201698.080
3Prison BreakTV-14Parents strongly cautioned. May be unsuitable ...90200898.080
4How I Met Your MotherTV-PGParental guidance suggested. May not be suitab...70201494.080

Tiene buena pinta: tienes títulos, clasificación por edades, año de estreno y la puntuación de usuarios, entre otras columnas. Antes de hacer el groupby y aplicar split-apply-combine, vamos a mirar los datos con más detalle para asegurarnos de que son lo que creemos y para tratar los valores ausentes. Observa que hay un valor ausente NaN en user_rating_score de la segunda fila (fila 1).

Resumir tus datos con gráficos y estadísticas

El método .info() de un DataFrame de pandas es oro puro. Aplicarlo a continuación muestra que tienes 1000 filas y 7 columnas, pero también que la columna de interés, user_rating_score, solo tiene 605 valores no nulos. Esto significa que hay 395 valores ausentes:

# Check out info of DataFrame
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 7 columns):
title                1000 non-null object
rating               1000 non-null object
ratinglevel          941 non-null object
ratingdescription    1000 non-null int64
release_year         1000 non-null int64
user_rating_score    605 non-null float64
user_rating_size     1000 non-null int64
dtypes: float64(1), int64(3), object(3)
memory usage: 54.8+ KB

Puedes eliminar las filas con valores ausentes, quitar duplicados y crear un pairplot del DataFrame con seaborn para hacerte una idea visual de los datos. Colorearemos por la columna 'rating'. Revisa los gráficos y piensa qué información puedes extraer.

# Drop rows with missing values and drop duplicate
df.dropna(inplace=True)
df.drop_duplicates(inplace=True)

# Visualize pairplot of df
sns.pairplot(df, hue='rating');

pairplot seaborn

Fíjate, por ejemplo, en user_rating_score en función de release_year. No hay una tendencia evidente a simple vista, pero quizá un poco de análisis de datos nos ayude a sacarla. Si quieres consultar varias estadísticas resumen del DataFrame, también puedes hacerlo con el método .describe():

# Get summary stats of df
df.describe()
 ratingdescriptionrelease_yearuser_rating_scoreuser_rating_size
count246.000000246.000000246.000000246.0
mean73.5569112010.27235881.39024480.0
std26.6161458.88721912.6778830.0
min10.0000001940.00000055.00000080.0
25%60.0000002007.00000071.00000080.0
50%80.0000002015.00000083.50000080.0
75%90.0000002016.00000092.75000080.0
max124.0000002017.00000099.00000080.0

Groupby y split-apply-combine para responder a la pregunta

Paso 1. Split

Ahora que ya has revisado los datos, vamos a la parte entretenida. Primero usarás un método groupby para dividir los datos en grupos, donde cada grupo es el conjunto de películas estrenadas en un año dado. Este es el split de split-apply-combine:

# Group by year
df_by_year = df.groupby('release_year')

Esto crea un objeto groupby:

# Check type of GroupBy object
type(df_by_year)
pandas.core.groupby.DataFrameGroupBy

Paso 2. Apply

Estos objetos groupby son muy útiles. ¿Recuerdas que el método .describe() para un DataFrame devuelve estadísticas resumen de las columnas numéricas? Pues bien, el método .describe() para objetos DataFrameGroupBy devuelve estadísticas resumen para cada columna numérica, pero calculadas para cada grupo del split. En tu caso, para cada release_year. Este es un ejemplo del apply en split-apply-combine: estás aplicando el método .describe() a cada grupo del groupby. Hazlo e imprime las 5 primeras filas del resultado:

# Summary stats over years
df_by_year.describe().head()
 ratingdescriptionuser_rating_scoreuser_rating_size
 countmeanstdmin25%50%75%maxcountmean...75%maxcountmeanstdmin25%50%75%max
release_year                     
19401.035.0NaN35.035.035.035.035.01.061.0...61.061.01.080.0NaN80.080.080.080.080.0
19781.060.0NaN60.060.060.060.060.01.086.0...86.086.01.080.0NaN80.080.080.080.080.0
19821.060.0NaN60.060.060.060.060.01.068.0...68.068.01.080.0NaN80.080.080.080.080.0
19861.035.0NaN35.035.035.035.035.01.067.0...67.067.01.080.0NaN80.080.080.080.080.0
19871.060.0NaN60.060.060.060.060.01.058.0...58.058.01.080.0NaN80.080.080.080.080.0

5 rows × 24 columns

Si quieres ver cómo queda la agrupación, puedes pasar el objeto groupby a la función list():

# Cast grouping as a list and check out one year
list(df_by_year)[10]
(1995,             title rating                                ratinglevel  \
 766         Balto      G  General Audiences. Suitable for all ages.   
 967  Heavyweights     PG              some rude language and pranks   

      ratingdescription  release_year  user_rating_score  user_rating_size  
 766                 35          1995               64.0                80  
 967                 60          1995               74.0                80  )

Paso 3. Combine

Imagina que quieres la media o la mediana de user_rating_score para cada año. Entonces puedes aplicar los métodos .mean() o .median(), respectivamente, al objeto groupby y "combinar" estos resultados en un nuevo DataFrame.

# Get median values by year and print first 5 rows
df_med_by_year = df_by_year.median()
df_med_by_year.head()
 ratingdescriptionuser_rating_scoreuser_rating_size
release_year   
194035.061.080.0
197860.086.080.0
198260.068.080.0
198635.067.080.0
198760.058.080.0

Hay un matiz importante respecto al índice del DataFrame df_med_by_year. Recuerda que el índice de un DataFrame son las etiquetas de fila. Fíjate en el índice del DataFrame original df:

# Print index of df
print(df.index)
Int64Index([  0,   2,   3,   4,   5,   6,   7,   8,   9,  10,
            ...
            908, 911, 917, 931, 962, 966, 967, 972, 973, 979],
           dtype='int64', length=246)

Este índice son los números de fila originales, etiquetados con enteros. Falta el '1' porque eliminaste algunas filas antes. El índice de df_med_by_year son los valores de la columna original por la que agrupaste, los años de release_year:

# Print index
print(df_med_by_year.index)
Int64Index([1940, 1978, 1982, 1986, 1987, 1989, 1990, 1992, 1993, 1994, 1995,
            1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
            2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017],
           dtype='int64', name='release_year')

Te interesa la columna user_rating_score, que contiene la mediana de la valoración de cada año. Puedes extraer la columna user_rating_score de df_med_by_year y representarla en función del año (dado por el índice del DataFrame df_rat_by_year):

# Slice out user rating and plot
df_rat_by_year = df_med_by_year['user_rating_score']
plt.scatter(df_rat_by_year.index, df_rat_by_year)
plt.xlabel('year of release')
plt.ylabel('median rating');

Netflix groupby split-apply-combine

Al mirar la figura, la mediana de la valoración aumenta claramente con el tiempo. Harían falta estadísticas más sofisticadas para convencerme de la tendencia en general, pero este es un buen ejemplo de cómo el análisis exploratorio es un gran punto de partida para seguir investigando.

Groupby y split-apply-combine en el día a día

Los objetos groupby no son intuitivos. Sin embargo, reflejan un acto natural: dividir un conjunto de datos con respecto a una de sus columnas (o a más de una, pero dejemos eso para otro post sobre agrupación por múltiples columnas e índices jerárquicos).

El principio split-apply-combine no solo es elegante y práctico, es algo que los Data Scientists usan a diario, como en el ejemplo anterior. Para apreciar más usos, echa un vistazo al artículo original de Hadley Wickham, The Split-Apply-Combine Strategy for Data Analysis. Si tienes ideas, comentarios o reflexiones, escríbeme en Twitter: @hugobowne.

Echa un vistazo a otros tutoriales prácticos de pandas en DataCamp, como:

Temas

Relacionado

Tutorial

Tutorial de unión de DataFrames en pandas

data-frames-in-python-banner_cgzjxy.jpeg

Tutorial

Tutorial de Pandas: DataFrames en Python

Tutorial

Tutorial seleccionar columnas con Python