Ga naar hoofdinhoud

Groupby, split-apply-combine en pandas

In deze tutorial leer je hoe je de pandas groupby-operatie gebruikt, gebaseerd op de bekende split-apply-combine-strategie, op Netflix-filmsdata.
· 8 min lezen

Verken met AI

Netflix heeft onlangs gebruikersbeoordelingen vrijgegeven. Ik wilde een eenvoudige vraag stellen: geven Netflix-abonnees de voorkeur aan oudere of nieuwere films?

Intuïtief wil je de dataset opsplitsen in groepen, één voor elk jaar, vervolgens een samenvattende statistiek berekenen, zoals het gemiddelde of de mediaan, en dan kijken of deze statistiek over de jaren toeneemt (daarna wil je misschien een statistische toets uitvoeren).

Het mooie is dat er een conceptueel kader voor bestaat, met implementaties in zowel Python als R. Dit kader staat bekend als "split-apply-combine" omdat we...

  • Stap 1: de data in groepen splitsen door een groupby-object te maken van de oorspronkelijke DataFrame;
  • Stap 2: een functie toepassen, in dit geval een aggregatiefunctie die een samenvattende statistiek berekent (je kunt in deze stap ook je data transformeren of filteren);
  • Stap 3: de resultaten combineren in een nieuwe DataFrame.

Dit is het conceptuele kader voor de analyse die we gaan doen. In deze post leer je hoe je dit toepast om de Netflix-vraag hierboven te beantwoorden met behulp van het Python-pakket pandas. Je kunt hetzelfde in R doen met bijvoorbeeld het pakket dplyr. Ik ga ook noodgedwongen in op groupby-objecten, die niet de meest intuïtieve objecten zijn. Het split-apply-combine-proces met groupby-objecten is een patroon dat we allemaal intuïtief uitvoeren, zoals we zullen zien, maar het was Hadley Wickham die de procedure in 2011 formaliseerde met zijn paper The Split-Apply-Combine Strategy for Data Analysis.

Als je deze techniek handig vindt, kun je er (naast veel andere zaken) meer over leren en ermee oefenen in onze cursus Manipulating DataFrames with pandas.

Data verkennen met pandas

Importeer je data

Hier gebruik je pandas, groupby-objecten en de principes van split-apply-combine om te bekijken hoe de Netflix-filmscores variëren als functie van het jaar waarin ze zijn uitgebracht. Ik kwam de data oorspronkelijk tegen op data.world hier. Je vindt alle code in deze post hier als je het wilt reproduceren.

Je importeert eerst de benodigde pakketten en de data en bekijkt de eerste vijf rijen van de data:

# 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

Dit ziet er prima uit: je hebt titels, classificaties, uitbrengjaar en gebruikersscore, naast diverse andere kolommen. Voordat we onze groupby- en split-apply-combine-procedure uitvoeren, kijken we iets nauwkeuriger naar de data om zeker te weten dat die is wat we denken en om met missende waarden om te gaan. Merk op dat er een missende waarde NaN staat in de user_rating_score van de tweede rij (rij 1).

Je data samenvatten met grafieken en statistieken

De pandas DataFrame-methode .info() is onmisbaar. Toepassen hieronder laat zien dat je 1000 rijen en 7 kolommen hebt, maar ook dat de kolom van belang, user_rating_score, slechts 605 niet-lege waarden heeft. Dit betekent dat er 395 missende waarden zijn:

# 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

Je kunt rijen met missende waarden droppen, dubbele rijen verwijderen en een pairplot van de DataFrame maken met seaborn om een visueel gevoel voor de data te krijgen. Je kleurt de data op basis van de kolom 'rating'. Bekijk de grafieken en kijk welke informatie je eruit haalt.

# 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

Bekijk bijvoorbeeld user_rating_score als functie van release_year. Er is geen visueel duidelijke trend, maar wellicht brengt wat data-analyse trends naar voren. Als je verschillende samenvattende statistieken van de DataFrame wilt bekijken, kan dat ook met de methode .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

Groupbys en split-apply-combine om de vraag te beantwoorden

Stap 1. Split

Nu je de data hebt verkend, is het tijd voor het leuke gedeelte. Je gebruikt eerst een groupby-methode om de data op te splitsen in groepen, waarbij elke groep de set films is die in een bepaald jaar zijn uitgebracht. Dit is de split in split-apply-combine:

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

Dit creëert een groupby-object:

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

Stap 2. Apply

Dergelijke groupby-objecten zijn erg nuttig. Weet je nog dat de methode .describe() voor een DataFrame samenvattende statistieken voor numerieke kolommen teruggeeft? Welnu, de methode .describe() voor DataFrameGroupBy-objecten geeft samenvattende statistieken voor elke numerieke kolom, maar dan per groep in de split. In jouw geval is dat per release_year. Dit is een voorbeeld van de apply in split-apply-combine: je past de methode .describe() toe op elke groep in de groupby. Doe dit en print de eerste 5 rijen van het resultaat:

# 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

Als je wilt zien hoe de groepering eruitziet, kun je het groupby-object doorgeven aan de functie 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  )

Stap 3. Combine

Stel dat je de gemiddelde of mediane user_rating_score per jaar wilt. Dan kun je respectievelijk de methode .mean() of .median() toepassen op het groupby-object en deze 'combineren' tot een nieuwe 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

Er is een belangrijk subtiliteit met betrekking tot de index van de DataFrame df_med_by_year. Denk eraan dat de index van een DataFrame uit de rijlijnen bestaat. Bekijk de index van de oorspronkelijke DataFrame 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)

Deze index bestaat uit de oorspronkelijke rijnummers, gelabeld met gehele getallen. '1' ontbreekt omdat je hierboven enkele rijen hebt gedropt. De index van df_med_by_year bestaat uit de waarden in de oorspronkelijke kolom waarop je hebt gegroepeerd, de jaren uit 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')

Je bent geïnteresseerd in de kolom user_rating_score, die de mediane beoordeling per jaar bevat. Je kunt de kolom user_rating_score uit df_med_by_year slicen en deze plotten als functie van het jaar (gegeven door de index van de 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

Kijkend naar de figuur neemt de mediane beoordeling duidelijk toe in de tijd. Je zou meer geavanceerde statistiek nodig hebben om me in het algemeen van de trend te overtuigen, maar dit is een voorbeeld van Exploratory Data Analysis als uitstekend startpunt voor verder onderzoek.

Groupbys en split-apply-combine in het dagelijks gebruik

Groupby-objecten zijn niet intuïtief. Ze komen echter overeen met de natuurlijke handeling om een dataset te splitsen ten opzichte van een van de kolommen (of meer dan één, maar dat bewaren we voor een andere post over groeperen op meerdere kolommen en hiërarchische indexen).

Het split-apply-combine-principe is niet alleen elegant en praktisch, het is iets wat Data Scientists dagelijks gebruiken, zoals in het bovenstaande voorbeeld. Om meer van de toepassingen te waarderen, bekijk Hadley Wickham's oorspronkelijke paper The Split-Apply-Combine Strategy for Data Analysis. Als je gedachten, reacties en/of overpeinzingen hebt, neem gerust contact op met me op Twitter: @hugobowne.

Bekijk ook DataCamp's andere handige pandas-tutorials, waaronder:

Onderwerpen

Gerelateerd