<jupyter_start><jupyter_text>Python en dataData science begint met data. Daarom richten we onze aandacht nu op technieken om met data om te gaan in Python. "Core Python" is daarvoor niet altijd direct geschikt, maar gelukkig bestaan er meerdere goed ontwikkelde packages die ons het leven makkelijker maken. Het gaat daarbij om het lezen, bewerken en weer opslaan van data. In de tussentijd zullen we alvast kleine uitstapjes maken naar de analyse van data.Het is "good practice" om paketten die je gaat gebruiken bovenaan je notebook allemaal tegelijk te importeren. Ik doe dat hier ook, zodat je meteen een overzicht krijgt van wat je te wachten staat.Vanaf dit notebook worden de instructienotebooks steeds meer alleen code met wat commentaar. Je wordt steeds beter in Python, dus code spreekt meer en meer voor zichzelf.<jupyter_code>import numpy as np          # Package voor numerieke bewerkingen, en het werken met arrays
import pandas as pd         # Package voor het werken met data in de vorm van tabellen. Pandas wordt je vriend!
import os<jupyter_output><empty_output><jupyter_text>Numerical Python: numpyVoor numeriek werk zoals elke data scientist, data-analist etc. iedere dag doet is numpy onmisbaar. Numpy wordt gewoonlijk geimporteerd als np, dus dat doen wij ook. Werken met arraysNumpy maakt gebruik van zogenaamde arrays. Deze lijken in de eerste plaats op lists, maar ze zijn heel wezenlijk anders:- Binnen een array zijn alle elementen van hetzelfde data type (dus geen mix zoals in lists)- Op arrays zijn de bewerkingen standaard array-operaties, wat betekent dat ze elementsgewijs worden uitgevoerd. Zo is, voor array a, 3*a gelijk aan een even lange array als a waarin alle elementen met 3 zijn vermenigvuldigd.Enkele voorbeelden:<jupyter_code>mijn_eerste_array = np.array([1, 2, 3, 4])
print(mijn_eerste_array)

keer3 = mijn_eerste_array * 3
# print(keer3)

# print(mijn_eerste_array + np.array([40, 30, 20, 10]))

# # Dingen die dus niet zouden kunnen, maar wel iets opleveren:
# huh = np.array([1, 2, 'help'])
# print(huh, "bestaat uit alleen maar strings omdat er daar minstens 1 van voorkomt!")
# Op arrays zijn bijzonder veel bewrekingen gedefinieerd. Veel zijn methoden van het array object, maar numpy kent ook functies die geen methode zijn.

print("Het totaal van mijn eerste array is", mijn_eerste_array.sum())
print("Het gemiddelde van mijn eerste array is", mijn_eerste_array.mean())
print("De mediaan van mijn eerste array is", np.median(mijn_eerste_array))
print()
# Let wel:
print("Zo kan het ook:", sum(mijn_eerste_array), np.sum(mijn_eerste_array))
# Arrays kunnen meer dan 1 dimensie hebben
dim2 = np.array([[1 , 2, 3],
                 [50, 60, 70]])   # Deze mag je op 1 regel definieren, maar dit maakt het leesbaarder
print(dim2)
# Op twee- en hogerdimensionale arrays kun je zulke dingen 
# over de hele array, of langs de rijen of kolommen doen:
print("De verschillende sommen van dim2:")
print("Totale som:", dim2.sum())
# print("Som per kolom:", dim2.sum(axis=0))
# print("Som per rij", dim2.sum(axis=1))
# Uiteraard zijn deze sommen numpy arrays:
sommetje = dim2.sum(axis=0)
print("Type", type(sommetje), "met shape", sommetje.shape)<jupyter_output><empty_output><jupyter_text>Missing data, NaN, ....Soms mist er data. Soms is een veld geen keurig getal meer (x/0, log(negatief getal), ...). Een datatype daarvoor in numpy is np.nan:<jupyter_code>arr = np.array([1, 2., np.nan, 17.])
print(arr)
# Aggregties hebben vaak een nan-safe variant, maar je moet wel weten wat ze betekenen!
print(arr.sum())
print(np.sum(arr))
print(np.nansum(arr))
# print(np.product(arr))
# print(np.nanprod(arr))
# print(np.mean(arr))
# print(np.nanmean(arr))
# print(np.nansum(arr)/len(arr))
# print(np.nansum(arr) / np.isfinite(arr).sum())<jupyter_output><empty_output><jupyter_text>Arrays maken zonder eerst een list te schrijvenEr bestaan functies die arrays genereren:<jupyter_code>print(np.zeros(3))
print(np.ones((2, 3)))
# print(np.arange(8, dtype=np.float))  # werkt ook zonder dtype, met stapjes etc.
# print(np.arange(2, 5, 0.3))
# print(np.random.random(size=2))
# print(np.random.random(size=(5, 3)))<jupyter_output><empty_output><jupyter_text>*** Index , slicing, selectieIndices werken heel vergelijkbaar met die in lists. In meerdere dimenses worden ze gescheiden door komma's, daarbinnen kun je precies dezelfde ranges etc. gebruiken. Let altijd op de volgorde van rijen en kolommen! Dit werkt ook in meerdere dimensies, maar dat is lastiger in tabelvorm weer te geven. Je kunt dergelijke indices ook gebruiken om array-elementen te veranderen. Let daarbij wel op het datatype!<jupyter_code>x = np.random.randint(0, 20, size=10)
print(x)
print(x[2])
print(x[2::2])
print(x[3:6])
print(x[-2:])
print()
x2 = np.random.randint(0, 20, size=(3,4))
# print(x2)
# print(x2[:,0])
# print(x2[0,:])
# print(x2[1:3, 1:3])
# print(x2[-1,-2:])
# print()
# x2[0,0] = 100
# print(x2)
# print()
# x2[0,0] = 17.72
# print(x2)  # alles na de komma wordt eraf gehaald!<jupyter_output><empty_output><jupyter_text>Let erop dat als je sub-arrays selecteert op deze manier, en die bewerkt, dat dan de oorspronkelijke array ook aangepast is! Dit kan heel handig zijn als je snel kleine stukken van een heel grote dataset wil manipuleren! Je kunt het voorkomen met .copy()<jupyter_code>print(x2)
print()
x3 = x2[1:3, 1:3]     # Probeer ook met x3 = x2[1:3, 1:3].copy()
print(x3)
print()
# x3[0,0] = 100
# print(x3)
# print()
# print(x2)<jupyter_output><empty_output><jupyter_text>*** Reshape, concatenation, splittingHet kan voor toepassingen handig zijn om verschillende tabellen van vorm te veranderen, aan elkaar te plakken of juist te splitsen. Ook hiervoor bestaan efficiente functinaliteiten in numpy.<jupyter_code># Reshape maakt van je array een andere vorm, wel even groot!
print(x2)
print()
print(x2.reshape(4, 3))  # ELementen "lopen gewoon door", "zoals je ze leest"
print()
# print(x2.reshape(6, -1)) # Met -1 bepaalt numpy welke dimensie je daar nodig hebt.
# print()
# print(x2.transpose())
# print()
# print(x2.reshape(12))
# print(x2.reshape(12)[np.newaxis, :])
# print(x2.reshape(12)[:,np.newaxis])
# np.concatenate plakt arrays aan elkaar.
x = np.array([1, 2, 3])
y = np.array([30, 20, 10])
print(np.concatenate([x, y]))
print(np.concatenate([x, y, y, y]))
print()
grid = np.array([[1, 2, 3],
                 [4, 5, 6]])
print(grid)
print()
print(np.concatenate([grid, 10*grid]))
print()
print(np.concatenate([grid, 10*grid], axis=1))
# Met np.split() en varianten daarop kun je juist je arrays in stukjes knippen.
x = [1, 2, 3, 99, 99, 3, 2, 1]
x1, x2, x3 = np.split(x, [3, 5])
print(x1, x2, x3)
print()
# grid = np.arange(16).reshape((4, 4))
# print(grid)
# print()
# boven, onder = np.vsplit(grid, [2])
# print(boven)
# print(onder)
# print()
# links, rechts = np.hsplit(grid, [2])
# print(links)
# print(rechts)<jupyter_output><empty_output><jupyter_text>*** BroadcastingBroadcasting betekent dat bewerkingen met arrays van verschillende dimensies worden gedaan. Het standaard voorbeeld is erg bekend, maar het kan dus ook op minder triviale manieren.<jupyter_code>x = np.array([2, 4, 6])
# Een scalar erbij optellen is broadcasting, de scalar wordt gebroadcast op elk element van de array:
print(x+3)
print()
# In meer dimensies:
y = np.ones((4, 3))   # Met de indices andersom werkt het dus niet!
print(y)
print()
print(x+y)<jupyter_output><empty_output><jupyter_text>Broadcasting kan nog veel complexer, maar in de praktijk kom je dt zelden tegen, we zullen het dus voorlopig hierbij laten. In de referenties staan werken met uitgebreidere voorbeelden uitgewerkt.*** Sorteren en set logicaPython komt met built-in "sort" en "sorted" functies. Die laten we echter voor wat ze zijn, omdat de numpy implementaties efficienter zijn. np.sort() gebruikte een efficient sorteer-algoritme, genaamd quicksort, maar kan als argument ook andere sorteeralgoritmen gebruiken (bijv. mergesort en heapsort). De werking is uiteraard simpel:<jupyter_code>x = np.array([2, 5, 3, 7, 3, 6])
print(np.sort(x))
print(x)<jupyter_output><empty_output><jupyter_text>Arrays hebben echter ook een sort-methode en deze sorteert volgens hetzelfde algoritme, maar verandert de array in het geheugen:<jupyter_code>x.sort()
print(x)<jupyter_output><empty_output><jupyter_text>In sommige gevallen is het praktischer om de indices van de gesorteerde array terug te vinden, in plaats van de gesorteerde variant van de lijst. Hiervoor bestaat np.argsort():<jupyter_code>x = np.array([2, 5, 3, 7, 3, 6])
print(x)
print(np.arange(len(x)))
print()
ind = np.argsort(x)
print(ind)
print(x[ind])   # Indices op deze manier gebruiken heet ook wel "fancy indexing".<jupyter_output><empty_output><jupyter_text>Op hogerdimensionale arrays moet je uiteraard weer assen aangeven via welke je ze wil sorteren. Let op dat rijen danwel kolommen dan onafhankelijk van elkaar gesorteerd worden!<jupyter_code>X = np.random.randint(0, 10, (4, 6))
print(X)
print()
print(np.sort(X, axis=0))
print()
print(np.sort(X, axis=1)) # Check ook de default waarde van het axis keyword. Wat betekent dat?<jupyter_output><empty_output><jupyter_text>In de verzamelingenleer ("set theory") bestaan een hoop functies die wel eens nuttig zijn wanneer je met arrays werkts. Hieronder zie je wat voorbeelden:<jupyter_code>values = np.array([6, 0, 0, 3, 2, 5, 6])
print(np.in1d(values, [2, 3, 6]))
print(np.unique(values))<jupyter_output><empty_output><jupyter_text>Functies binnen numpyNumpy heeft ook een hele berg (rekenkundige) functies ingebouwd die werken op de arrays die erin gaan. Hieronder gewoon een paar simpele voorbeelden<jupyter_code>x = np.random.randint(0, 10, 6)
print(x)
print(np.sqrt(x))
print(np.sin(x))
print(np.mod(x, 4))
print(np.clip(x, 4, 8))<jupyter_output><empty_output><jupyter_text>Pandas: Werken met tabellenTweedimensionale numpy arrays zijn natuurlijk tabellen. Maar zou het niet handig zijn als kolommen namen hebben, de rijen handige indices die iets betekenen en je allerlei selecties en bewerkingen kunt doen gewoon met die namen. Pandas is een enorm populair package, gemaakt door <NAME> die het wilde hebben voor zijn werk in de financiele wereld, dat precies dat doet. We gaan hier uitgebreid op de functionaliteiten in. Zoals gebruikelijk: er is nog veel meer! Ook dit boek staat volledig als notebooks online:https://github.com/wesm/pydata-book (alleen de tekst ertussen mist, maar de voorbeelden spreken veelal voor zich). SeriesDe pandas-variant van eendimensionale arrays heet Series. Er bestaan meerdere verschillen met numpy arrays, maar het belangrijkste: Indices zijn expliciet. Numpy kent impliciete indices die uniek zijn en optellen vanaf 0. In Series kunnen ze van alles zijn en hoeven ze niet uniek te zijn. Een serie kan gemaakt worden van objecten met of zonder index:<jupyter_code>mijn_eerste_serie = pd.Series([12, 13, 14])   # Indices automatisch gegenereerd "a la numpy".
print(mijn_eerste_serie)
print()
# mijn_eerste_serie = pd.Series([12, 13, 14], index=['nul', 'een', 'twee'])   # Indices opgegeven
# print(mijn_eerste_serie)
# print()
# mijn_eerste_serie = pd.Series(np.array([12, 13, 14]), index=['nul', 'een', 'twee'])   # Werkt ook met numpy arrays
# print(mijn_eerste_serie)
# print()
# mijn_eerste_serie = pd.Series([12, 13, 14], index=['nul', 'een', 'een'])   # Indices hoeven niet uniek te zijn
# print(mijn_eerste_serie)
# print()
# mijn_eerste_serie = pd.Series({'dictnul':101, 'dicteen':202, 'dicttwee':303})   # Vanuit een dict worden keys de indices
# print(mijn_eerste_serie)
# print()<jupyter_output><empty_output><jupyter_text>Selectie is bijzonder eenvoudig, als je gebruikt maakt van de index! Sorteren kan op waarden, of op index.<jupyter_code>mijn_eerste_serie = pd.Series([12, 13, 14, 33, 34., 35], index=['nul', 'een', 'een', 34, 35, 36]) # Indices hoeven niet hetzelfde datatype te hebben!
print(mijn_eerste_serie)   # Zoals in numpy: alle elementen van "meest complexe datatype"
print()
print(mijn_eerste_serie['een'])   # Geeft alle elementen met de betreffende index!
print('een' in mijn_eerste_serie)
print(11 in mijn_eerste_serie)    # De in-operatie zoekt naar indices, niet naar waarden.<jupyter_output><empty_output><jupyter_text>*** Je nieuwe vriend: het DataFrameEen DataFrame is een tabellarische datacontainer, tweedimensionaal. Het is eigenlijk niks meer dan een verzameling series met een gemeenschappelijke index. Wanneer geen kolomnamen worden opgegeven worden deze ook gewoon gevuld met 0, 1, 2 etc, net zoals voor de rijen geldt.<jupyter_code>mijn_eerste_df = pd.DataFrame(np.random.random(size=(3, 4)))
print(mijn_eerste_df)
mijn_eerste_df.head()    # head en tail laten de boven- resp. onderkant van de DataFrame zien. Wel een iets mooiere layout dan print.
mijn_eerste_df = pd.DataFrame(np.random.random(size=(4, 3)), columns=['x', 'y', 'z'])
mijn_eerste_df

# Gaat ook heel natuurlijk vanuit een dict
mijn_eerste_df = pd.DataFrame({'eerste kolom':[1, 2, 3], 'tweede kolom':[23, 45, 67], 'derde kolom':['aap', 'noot', 'mies']}, index=[10,20, 30])
mijn_eerste_df<jupyter_output><empty_output><jupyter_text>Indices voor rijen, kolommen, filters en selectiesWe zagen al dat indices voor Series handig zijn om rijen te selecteren. Voor DataFrames is dat niet anders. Er kan ook op kolomnamen worden geselctereed, gefilterd, etc. Elke kolomnaam zonder spatie, die begint met een letter is ook een attribuut geworden van je DataFrame, wat selectie heel handig maakt.<jupyter_code>mijn_eerste_df = pd.DataFrame({'eerste_kolom':[1, 2, 3], 'tweede_kolom':[23, 45, 67], 'derde kolom':['aap', 'noot', 'mies']}, index=[10,20, 30])
mijn_eerste_df['eerste_kolom'] 
print(type(mijn_eerste_df.eerste_kolom))
mijn_eerste_df.eerste_kolom


# Indices kunnen als kolom gebruikt worden, zodat er een nieuwe index 0, 1, 2, ... ontstaat met reset_index
mijn_eerste_df.reset_index()  # Check de help: inplace=True is nu nog handig. drop=True laat de index verdwijnen

# Je kunt ook een andere kolom als index gebruiken:
# mijn_eerste_df.set_index('derde kolom', drop=False)  # drop=True is hier de standaard! Oorspronkelijke index verdwijnt.
# In bijna alle functie met een "inplace=False" default, wordt deze binnenkort in de nieuwste pandas de default!
# Kolomnamen maken filtering heel erg makkelijk:
mijn_eerste_df[mijn_eerste_df.eerste_kolom > 1]
# mijn_eerste_df[((mijn_eerste_df.eerste_kolom > 1) & (mijn_eerste_df["derde kolom"] != 'mies'))]

# Als je na zo'n selectie alleen indices wilt:
mijn_eerste_df[mijn_eerste_df.eerste_kolom > 1].index  # Zet daar nog een .values achter als je geinteresseerd bent in de waarden van de indices<jupyter_output><empty_output><jupyter_text>Selectie met loc en ilocIndices kunnen soms licht verwarrend werken. Zo zijn expliciete en impliciete indices toegstaan in pandas:<jupyter_code>data = pd.Series(['a', 'b', 'c', 'd', 'e', 'f'], index=[1, 3, 5, 7, 9, 11])
print(data)
print()
print(data[1])    # expliciet, de waarde die bij index 1 hoort
print()
print(data[3:6])  # impliciet, er wordt nu wel langs de elementen "geteld"<jupyter_output><empty_output><jupyter_text>Om deze verwarring te voorkomen bestaat de "loc" indexer, die altijd gaat over de echte indices. Tegenhanger iloc daarenetegen gebruikt impliciete "Python style" indices. Vanwege het duidelijke en eenduidige karakter van deze twee indexers, is het aan te raden om altijd deze vorm te gebruiken als je duidelijke en onderhoudbare code wilt schrijven. Onder de simpele voorbeelden met een pd.Series wordt een DataFrame gedefinieerd om deze functionaliteiten ook daarop te introduceren.<jupyter_code>data = pd.Series(['a', 'b', 'c', 'd', 'e', 'f'], index=[1, 3, 5, 7, 9, 11])
print(data.loc[5])
print(data.iloc[2])

print()
# area = pd.Series({'California': 423967, 'Texas': 695662,
#                   'New York': 141297, 'Florida': 170312,
#                   'Illinois': 149995})
# pop = pd.Series({'California': 38332521, 'Texas': 26448193,
#                  'New York': 19651127, 'Florida': 19552860,
#                  'Illinois': 12882135})
# data = pd.DataFrame({'area':area, 'pop':pop})
# print(data)

# print()

# print(data.area)   # Probeer dat eens met de andere kolom! print(data.pop)
# print()

# data['density'] = data['pop'] / data['area']
# print(data)
# print()
# print(data.iloc[:3, :2])    # iloc laat de DataFrame als een simpele numpy array werken.
# print()
# print(data.loc['Texas':'Florida', :'pop'])<jupyter_output><empty_output><jupyter_text>Omdat het soms verwarrend is wanneer rijen, en wanneer kolommen bedoeld worden:- Indexing gaat over kolommen, slicing over rijen.- Dit kan ook met np.array-achtige stijl worden genoteerd- Selectie met voorwaarden gaan op dezelfde manier.<jupyter_code>print(data["Texas":"New York"])
print()
print(data[1:4])
print()
print(data[data.density > 100])<jupyter_output><empty_output><jupyter_text>Multi-indexJe kunt ook indices met meer dan 1 niveau hebben.<jupyter_code>index = [('California', 2000), ('California', 2010),
         ('New York', 2000), ('New York', 2010),
         ('Texas', 2000), ('Texas', 2010)]
index = pd.MultiIndex.from_tuples(index)
print(index)
# print()
# populations = [33871648, 37253956,
#                18976457, 19378102,
#                20851820, 25145561]
# pop = pd.Series(populations, index=index)
# print(pop)
# print()

# print(pop[:, 2010])


# De multi-index kan ook gebruikt worden om kolommen te maken:
pop_df = pop.unstack()
print(pop_df)
print()
pop_df.stack()

# Een dataframe maken met de nieuwe multi-index is net zo makkelijk als het toevoegen van een kolom anders zou zijn:
pop_df = pd.DataFrame({'total': pop,
                       'under18': [9267089, 9284094,
                                   4687374, 4318033,
                                   5906301, 6879014]})
pop_df


# De index-levels kunnen ook namen krijgen
pop.index.names = ['state', 'year']
pop
# En ze werken ook voor kolommen
index = pd.MultiIndex.from_product([[2016, 2017], ['Winter', 'Zomer']],
                                   names=['Jaar', 'Doktersbezoek'])
columns = pd.MultiIndex.from_product([['Jan', 'Kees', 'Suus'], ['Hartslag', 'Temperatuur']],
                                     names=['Patient', 'Onderzoek'])

# Namaakdata
data = np.round(np.random.randn(4, 6), 1)
data[:, ::2] *= 10
data += 37

# DataFrame!
gezondheid = pd.DataFrame(data, index=index, columns=columns)
gezondheid
gezondheid['Kees']
# gezondheid.loc[2016]
# gezondheid.loc[:, ('Jan', 'Hartslag')]<jupyter_output><empty_output><jupyter_text>Beschrijvende statistiekOmdat pandas gemaakt is om met kwantitatieve data te werken is het eenvoudig om allerlei beschrijvende statistieken uit je data te halen. Een soort samenvatting van je hele DataFrame kun je eenvoudig krijgen met .describe(). Andere functies, zoals alle numpy en scipy functies werken moeiteloos op kolommen van DataFrames.<jupyter_code>mijn_eerste_df = pd.DataFrame({'eerste_kolom':[1, 2, 3], 'tweede_kolom':[23, 45, 67], 'derde kolom':['aap', 'noot', 'mies']}, index=[10,20, 30])
mijn_eerste_df.describe()   # Informatie over alle numerieke kolommen.
# np.nanmean(mijn_eerste_df.eerste_kolom)
# np.nanmean(mijn_eerste_df.loc[:,('eerste_kolom', 'tweede_kolom')])<jupyter_output><empty_output><jupyter_text>Data laden en wegschrijvenVaak krijg je data van anderen angeleverd, of bereid je wellicht zelf met andere tools data voor. Deze data komt dan in een dataformaat dat moet worden gelezen ni Python, bijvoorbeeld met pandas, om er verder mee aan de slag te kunnen. Python kan gemakkelijk platte tekst lezen en ddar numerieke data van maken, maar we gaan er voor deze cursus van uit dat de data wordt aangeleverd in een gestandaardiseerd formaat, zoals bijvoorbeeld csv, excel, json, xml of hdf5. In deze instructie spelen we met excel en csv om dat deze formaten in veel bedrijven gangbaar zijn. Heb je moeite met het lezen van je eigen dataformaat, neem dan vooral even contact op met de cursusleider (op een cursusavond, of via <EMAIL>).Wanneer je begint te typen met "pd.read_" en dan op tab drukt zie je vanzelf wat er gelezen kan worden met standaard pandas functies. Het nu volgende voorbeeld gebruikt een csv, maar voor het lezen van excel gaat het op dezelfde manier.<jupyter_code>koffiefile = os.path.join('data', 'csvoorbeeld.csv')
koffie = pd.read_csv(koffiefile, )   # check de help voor alle opties!
koffie<jupyter_output><empty_output><jupyter_text>Wegschrijven gaat op een manier die vergelijkbaar is met lezen. Je moet een file definieren en er een object naartoe schrijven met een geschikte methode. Pandas heeft er opnieuw een hoop. Een voorbeeld om bovenstaand DataFrame over koffie naar een excel-file te schrijven:<jupyter_code>writer = pd.ExcelWriter(os.path.join('data', 'koffie_in_excel.xlsx'))
koffie.to_excel(writer, 'Lekker!')
writer.save()<jupyter_output><empty_output><jupyter_text>Data combinerenZeer zelden zit alle data die je nodig hebt al in 1 tabel. Denk bijvoorbeeld aan een database vol met verkochte goederen. Hierin is meestal alleen een klant-ID opgenomen, zodat in een andere database (of een andere tabel) de data over die klant (leeftijd, geslacht, betaalhistorie) teruggevonden kan worden, en deze informatie niet voor iedere transactie met dezelfde klant herhaald hoeft te worden.Hier breid ik het koffie-voorbeeld uit met wat extra informatie over de merken in de tabel (disclaimer: ik ben niet betaald door koffieverkopers... en het is allemaal slechts de mening van een simpele data scientist, of fictioneel). Ik laat er expres 1 uit, en stop er een andere bij.<jupyter_code># Definieer een DataFrame met informatie per merk:
koffiemerken = pd.DataFrame({'Merk':['Palazzo', "G'woon", "L'Or", "Nescafe"], "Land":['Nederland', 'Nederland', 'Frankrijk', 'Duitsland']})
koffiemerken
# Voeg deze data van landen toe aan de tabel hierboven
koffie.merge(koffiemerken)
koffietotaal = koffie.merge(koffiemerken, how='left')   # En andere methoden!
koffietotaal<jupyter_output><empty_output><jupyter_text>Aggregeren en groupbySoms wil je data groeperen per categorie en over die categorieen geaggregeerde data verzamelen. Zo kun je bijvoorbeeld benieuwd zijn naar de gemiddelde beoordeling per merk of land, of het aantal verschillende smaakjes per intensiteit.<jupyter_code>per_land = koffietotaal.groupby('Land')
per_land
print(per_land.beoordeling.mean())
print()
print(per_land.beoordeling.count())<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1097708082026538, 'fraction_numerical': 0.02600187642407184, 'mean_word_length': 5.300028145229383, 'pattern_counts': {'":': 2, '<': 111, '<?xml version=': 0, '>': 115, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 1}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '174443', 'license': 'MIT', 'n_tokens_mistral': 8648, 'n_tokens_neox': 8172, 'n_words': 2589, 'path': 'Instructie_2.ipynb', 'repo_name': 'harcel/PyDataScienceIntroNL', 'size': '22408'}