Pylväskaavioita Pythonilla

Tämän artikkelin pylväskaaviot pohjautuvat artikkelissa Lukumääriä ja prosentteja laskettuihin lukumäärä- ja prosenttitaulukoihin, joiden laskemista en tässä enää uudelleen selitä.

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/bar.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Johdatus kaavioiden laatimiseen

Kun käytän Pandas-ohjelmakirjastoa datojen analysointiin, niin sijoitan datat ja lasketut tulostaulukot dataframe-tyyppisiin muuttujiin. Voit ajatella dataframen taulukoksi. Dataframesta voin laatia kaavioita. Kaavioiden laadinnassa Pandas hyödyntää matplotlib.pyplot-ohjelmakirjastoa. Aluksi tuon tarvittavat ohjelmakirjastot. Vakiintuneen tavan mukaisesti käytän matplotlib.pyplot sijasta lyhennettä plt.

import pandas as pd
import matplotlib.pyplot as plt

Mukavuussyistä määrittelen grafiikat tulostettavaksi automaattisesti Jupyter notebookiin:

%matplotlib inline

Jos jätän %matplotlib inline -määrittelyn pois, niin jokaisen kaavion jälkeen tarvitsen komennon plt.show() kaavion näyttämiseksi Jupyter notebookissa.

Jos kaavioiden oletustyyli ei miellytä minua, niin voin käyttää tyylimäärittelyä. Esimerkiksi:

plt.style.use('seaborn-whitegrid')

Erilaisiin tyyleihin voit tutustua osoitteessa

https://matplotlib.org/devdocs/gallery/style_sheets/style_sheets_reference.html

Kaavion laadin komennolla df.plot. barh() (vaakapylväät), df.plot.bar() (pystypylväät) tai df.plot.hist() (histogrammi). Käytän tietysti df sijasta dataframea, josta haluan kaavion laatia. Jos en halua kaavioon kaikkia dataframen sarakkeita, niin nimeän kuvattavat sarakkeet, esimerkiksi df[’ikä’].plt.hist().

Transpose()-funktiolla voin vaihtaa dataframen rivit ja sarakkeet päittäin kaaviota varten, esimerkiksi df.transpose().plot.bar().

Kaaviotyypin jälkeisten sulkujen sisälle voin kirjoittaa lisämäärittelyitä, esimerkiksi pylväiden värin: df.plot.barh(color=’darkgreen’). Väreihin voit tutustua esimerkiksi osoitteessa

https://stackoverflow.com/questions/22408237/named-colors-in-matplotlib

Kaavion laatimiskomento, esimerkiksi df.plot.bar(), palauttaa Axes-objektin, jonka kautta monia kaavion yksityiskohtia voidaan viimeistellä. Jos tarvitsen Axes-objektia, niin tallennan sen muuttujan arvoksi, esimerkiksi ax=df.plot.bar().

Frekvenssit vaakapylväinä

Dataframe df1 sisältää koulutuksen frekvenssitaulukon:

bar1

Laadin vaakapylväskaavion:

df1['%'].plot.barh(color='C0')
plt.xlabel('prosenttia (n='+str(n)+')')

Outo värimääritys color=’C0′ vaatinee selitystä. Jos teen kuvion vain yhdestä dataframen sarakkeesta (esimerkiksi df1[’%’]) niin kyseessä ei ole enää dataframe, vaan series. Tällöin jokainen pylväs esitetään omalla värillään (vanhemmissa versioissa tällaista ominaisuutta ei ollut). Verkkokeskusteluiden perusteella on epäselvää, onko kyseessä virhe vai tarkoituksellinen ominaisuus. Esimerkin tapauksessa värit eivät mielestäni tuo lisäarvoa. Värimääritys color=’C0′ pakottaa pylväisiin käytössä olevan väripaletin ensimmäisen värin. Vastaavasti ’C1’ pakottaisi käytössä olevan väripaletin toisen värin jne.

Funktiolla plt.xlabel() voin lisätä vaaka-akselille otsikon. Esimerkin tapauksessa olen aiemmin koodissa laskenut n-arvon: n=df1[’lkm’].sum(). Hyödynnän n-arvoa merkkijonoksi str(n) muunnettuna.

bar2

Histogrammi

Iän histogrammin voin laskea suoraan alkuperäisestä aineistosta df:

df['ikä'].plot.hist(bins=[20,30,40,50,60,70], 
   edgecolor='grey')
plt.xlabel('Ikä')
plt.ylabel('Lukumäärä')

Histogrammin lisämäärityksenä annan luokkarajat (bins). Käytännössä luokat ovat [20,30); [30,40); [40,40); [50,60); [60,70]. Tavallinen sulkumerkki tarkoittaa, että arvo ei kuulu kyseiseen luokkaan: esimerkiksi arvo 30 ei kuulu luokkaan [20,30).

Jos en määritä edgecolor-arvoa, niin pylväiden väliset rajaviivat eivät erotu, koska oletuksena reunaviiva on samanvärinen kuin pylväs.

bar3

 

Kumulatiivinen histogrammi prosentteina

Kumulatiivisen histogrammin laatimiseksi käytän lisämäärettä cumulative=True. Prosentit (desimaalilukuna) saan lisämääreellä normed=1.

ax=df['ikä'].plot.hist(cumulative=True, 
   bins=[20,30,40,50,60,70], normed=1, edgecolor='grey')
plt.xlabel('Ikä')
plt.ylabel('Prosenttia')

vals = ax.get_yticks()
ax.set_yticklabels(['{:.0f} %'.format(y*100) 
    for y in vals])

Jos käytän histogrammin pystyakselilla prosentteja, niin niiden ulkoasun muotoiluun tarvitsen Axes-objektia. Tämän vuoksi sijoitan kaavion laadinnassa palautuvat objektin muuttujan ax arvoksi.

Pystyakselin prosentit tallennan vals-muuttujan arvoksi ja ax.set_yticklabels()-funktiolla muotoilen prosenttien ulkoasun. Sadalla kertomista (y*100) tarvitsen, koska prosenttiluvut ovat alunperin desimaalimuodossa. En näytä desimaaleja (.0f) ja perään laitan välilyönnin ja %-merkin.

bar4

Monivalintakysymyksen valintojen lukumäärät

Dataframe df5 sisältää monivalintakysymyksen valintojen lukumäärät:

bar5

Laadin vaakapylväskaavion:

df5.plot.barh(color='darkgreen')

 

bar6

Ristiintaulukointi pylväinä

Dataframe df6 sisältää koulutuksen ja sukupuolen ristiintaulukoinnin:

bar7

Laadin ristiintaulukoinnista vaakapylväskaavion:

ax=df6.plot.barh(color=['blue','red'])
plt.xlabel('Lukumäärä')

handles, labels = ax.get_legend_handles_labels()
ax.legend(reversed(handles), reversed(labels), loc='best')

Oletusvärien sijasta määritän, että ensimmäisen arvosarjan (mies) väriksi tulee sininen ja toisen arvosarjan (nainen) väriksi punainen.

Viheliäisenä yksityiskohtana pylväissä naisten pylväs on ylimpänä, mutta miesten väri on selitteessä ylimpänä. Mielestäni on katsojalle selkeämpää, jos järjestys on sama sekä pylväissä että selitteessä. Tämän korjaamiseksi vaihdan selitteiden järjestyksen (reversed(handles), reversed(labels)). Lisäksi määritän selitteen sijainniksi ’best’, joka useimmissa tapauksissa sijoittaa selitteen sopivaan vapaaseen paikkaan.

bar8

Ristiintaulukointi pinottuna 100 % pylväskaaviona

Dataframe df7 sisältää koulutuksen ja sukupuolen välisen ristiintaulukoinnin prosentteina sukupuolesta.

bar9

Pylväskaavioon en halua yhteensä-saraketta. Lisäksi haluan sukupuolet luokiksi (ei arvosarjoiksi). Teen siis kuvion taulukosta df7[[’mies’,’nainen’]].transpose():

ax=df7[['mies','nainen']].transpose().plot.barh(stacked=True)
plt.xlabel('Prosenttia sukupuolesta')

plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.2), 
   ncol=4)

vals = ax.get_xticks()
ax.set_xticklabels(['{:.0f} %'.format(x*100) for 
   x in vals])

Pinotut pylväät saan lisämääreellä stacked=True.

Pinottu pylväskaavio valtaa koko kuva-alueen, jonka vuoksi selitteelle on vaikeaa löytää sopivaa sijaitia. Siirsin selitteen koko kuvion ulkopuolelle: plt.legend(loc=’upper center’, bbox_to_anchor=(0.5, 1.2), ncol=4). Bbox_to_anchor määrittää selitelaatikon sijainnin vaakasuunnassa (0.5) ja pystysuunnassa (1.2). Sopivien arvojen löytämiseksi jouduin suortittamaan useita kokeiluita. Ncol määrittää selitteen sarakkeiden määrän. Määrittämällä sarakkeiden määräksi arvosarjojen (koulutusten) lukumäärän (4) saan kaikki selitteen arvot vierekkäin.

Akselin prosenttilukujen ulkoasun muotoilen jo aiemmin esiin tulleella tavalla.

bar10

Monivalinnan vaihtoehtojen lukumäärät taustamuuttujan mukaan

Dataframe df9 sisältää monivalintakysymyksen valintojen lukumääriä sukupuolen mukaan ryhmiteltyinä.

bar11

Laadin pylväskaavion:

df9.plot.bar()
plt.ylabel('lukumäärä')
plt.xticks(rotation='horizontal')

Käännän luokka-akselin otsikot vaaka-asentoon (plt.xtics(rotation=’horizontal’))

bar12

Dataframe df10 sisältää äskeisen tiedon prosentteina sukupuolesta.

bar13

Laadin pylväskaavion:

ax=df10.plot.bar()
plt.ylabel('prosenttia sukupuolesta')
plt.xticks(rotation='horizontal')

plt.legend(loc='upper center', bbox_to_anchor=
   (0.5, 1.2), ncol=4)

vals = ax.get_yticks()
ax.set_yticklabels(['{:.0f} %'.format(x*100) 
   for x in vals])

Jo aiemmin opitulla tavalla siirrän selitteen kaavion ulkopuolelle (plt.legend(…) ja muotoilen prosenttilukujen ulkoasun (ax.set_yticklabels(…).

bar14

Vaihtoehtoisesti voin toteuttaa kaavion myös seuraavasti:

ax=df10.transpose().plot.bar()
plt.ylabel('prosenttia sukupuolesta')
plt.xticks(rotation='horizontal')

vals = ax.get_yticks()
ax.set_yticklabels(['{:.0f}%'.format(x*100) 
   for x in vals])

bar19

Mielipiteiden jakaumat

Dataframe df11 sisältää koontitaulukon mielipiteiden lukumääristä eri asioiden kohdalla.

bar15

Laadin pinotun vaakapylväskaavion:

df11.transpose().plot.barh(stacked=True, color=['darkred',
   'red','grey','blue','darkblue'])
plt.xlabel('Lukumäärä')
plt.legend(bbox_to_anchor=(1, 0.5))

bar17

Voin myös laatia pylväskaavion jokaisen asian osalta:

df11.plot.bar(subplots=True, layout=(1,5), sharex=True, 
   sharey=True, figsize=(12,2), color='maroon', legend=False)

Kuvion laadinnassa syntyy kaksi objektia: Figure-objekti ja Axes-objekti. Axes-objekti sisältää yksittäisen kaavion sisällön. Figure-objekti sisältää Axes-objektin, mutta voi sisältää myös useita Axes-objekteja.

  • Lisämääreellä subplots=True määritän, että Figure-objektiin sijoitetaan useita kaavioita.
  • Lisämääreellä layout=(1,5) määritän, että kaaviot sijoitetaan 1 riville, 5 rinnakkain.
  • Lisämääreillä sharex=True, sharey=True määritän, että kaikilla kaavioilla on yhteiset akselit.
  • Lisämääreellä figsize=(12,2) määritän kuvion koon.
  • Lisämääreellä color=’maroon’ määritän pylväiden väriksi maroon.
  • Lisämääreellä legend=False jätän kaavioista selitteet pois.

bar16

 

Mielipiteiden keskiarvot pylväinä

Dataframe df12 sisältää mielipiteiden keskiarvoja. Laadin pylväskaavion:

ax=df12.plot.barh(color='C0')
plt.xlim(1,5)
plt.title('Tyytyväisyyskeskiarvoja')

ax.set(xticks=[1,2,3,4,5]) 
ax.set_xticklabels(['erittäin tyytymätön','tyytymätön',
   'neutraali', 'tyytyväinen','erittäin tyytyväinen'], rotation=30)

Lisäasetuksella plt.xlim(1,5) asetan arvoakselin pienimmän ja suurimman arvon (tässä mielipideasteikko oli 1 – 5).

Lisään kaavioon pääotsikon (plt.title(’Tyytyväisyyskeskiarvoja’)).

Määritän akselin jaotuksen (ax.set(xticks=[1,2,3,4,5])) ja korvaan akselin numerot mielipiteiden nimillä (ax.set_xticklabels(…). Akselin otsikot käännän 30 asteen kulmaan.

bar18

 

 

Mainokset

Lukumääriä ja prosentteja

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/lkm.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Lukumäärä- ja prosenttiyhteevetojen laskenta on datan kuvailun keskeisin menetelmä. Kyselytutkimusdatan kuvailussa saatetaan jopa tyytyä pelkkiin lukumäärä- ja prosenttiyhteevetoihin. Tässä artikkelissa käytän Pythonia ja Pandas-kirjastoa lukumäärien ja prosenttien laskentaan eri tyyppisissä tilanteissa:

  • frekvenssitaulukko kategoriselle muuttujalle
  • frekvenssitaulukko määrälliselle muuttujalle, joka luokitellaan ennen frekvenssitaulukon laskentaa
  • monivalintakysymyksen valintojen taulukointi lukumäärinä ja prosentteina
  • lukumäärien ja prosenttien laskenta kahden muuttujan ristiintaulukoinnissa
  • lukumäärien ja prosenttien laskenta ristiintaulukoinnissa, jossa toisena muuttujana määrällisestä muuttujasta luokiteltu muuttuja
  • monivalintakysymyksen valintojen taulukointi lukumäärinä ja prosentteina toisen muuttujan määräämissä ryhmissä
  • samalla mielipideasteikolla mitattujen mielipiteiden yhdistäminen koontitaulukkoon.

Laskennassa tarvittavat Pythonin ja Pandas-kirjaston toiminnot

Lukumäärät voin laskea joko value_counts-funktiolla tai pd.crosstab-funktiolla. Value_counts-funktio antaa tulokset series-muodossa. Tulosten jatkotyöstöä ajatellen tulokset kannattaa muuttaa dataframeksi to_frame-funktiolla. Pd.crosstab antaa tulokset suoraan dataframe-muodossa.

Prosenttien ja kertymäprosenttien laskentaan tarvitsen sum– ja cumsum -funktioita. Monivalintojen tapauksessa käytän df.shape[0]-funktiota vastaajien kokonaislukumäärän selvittämiseksi ja groupby-funktiota toisen muuttujan mukaan ryhmittelemiseen.

Taulukoiden viimeistelyssä muokkaan rivien (index) ja sarakkeiden (columns) otsikoita. Tuloksena esitettäviä lukuja tyylittelen joko pyöristämällä round-funktiolla tai muotoilemalla style.format-funktiolla. Pyöristäminen vaikuttaa taulukon lukujen todelliseen tarkkuuteen, mutta style.format vaikuttaa vain näkyvän taulukon esitystapaan.

Datan avaaminen

Tarvitsen pandas-kirjaston toimintoja, joten ensimmäiseksi tuon pandas-kirjaston käyttööni. Esimerkkinä käyttämäni Excel-aineisto sisältää erään yrityksen työntekijöiltä kerättyjä tietoja, kuten voit df.head() -toiminnon tulosteesta lukea.

import pandas as pd
df = pd.read_excel('http://taanila.fi/data1.xlsx', 
   sheet_name = 'Data')
df.head()

Frekvenssitaulukko kategoriselle muuttujalle

Datasta löytyy muiden muassa koulutus-muuttuja, jonka mahdolliset arvot ovat 1 = peruskoulu, 2 = 2. aste, 3 = korkeakoulu ja 4 = ylempi korkeakoulu. Voin laskea koulutukselle frekvenssitaulukon joko value_counts-funktiolla tai pd.crosstab-funktiolla.

Frekvenssit value_counts-funktiolla

Value_counts-funktio järjestää oletuksena tulosteet frekvenssien mukaiseen järjestykseen. Tässä haluan tulosteet koulutuksen mukaiseen järjestykseen (1, 2, 3, 4), joten käytän lisäargumenttia sort=False.

Value_counts-tuloste ei ole dataframe, mutta muutan sen sellaiseksi to_frame()-funktiolla.

Numeroiden 1, 2, 3, 4 sijasta saan taulukkoon koulutusten nimet asettamalla ne indeksin arvoiksi (df1.index=…).

df1 = df['koulutus'].value_counts(sort = False).to_frame()
df1.index = ['peruskoulu', '2.aste', 'korkeakoulu',
   'ylempi korkeakoulu']
df1

Tuloste näyttää seuraavalta:

lkm1

Seuraavaksi korvaan koulutus-otsikon lkm-otsikolla ja lasken prosentit lukumäärien viereen:

df1 = df1.rename(columns = {'koulutus': 'lkm'})
df1['%'] = df1['lkm'] / df1['lkm'].sum() * 100
df1

Tuloste näyttää seuraavalta:

lkm2

Täydennän vielä taulukkoa kertymäprosenteilla ja pyöristän prosenttiluvut yhden desimaalin tarkkuuteen. Huomaa, että pyöristäminen muuttaa prosenttiluvut pysyvästi. Myöhemmin tässä artikkelissa käytän style.format-toimintoa, jolla voin näyttää halutun määrän desimaaleja muuttamatta taustalla olevia lukuarvoja.

df1['kertymä %'] = df1['%'].cumsum().round(decimals = 1)
df1['%'] = df1['%'].round(decimals = 1)
df1

Tuloste näyttää seuraavalta:

lkm3

Frekvenssit crosstab-funktiolla

Frekvenssitaulukon laskeminen crosstab-funktiolla sujuu pääpiirteissään samojen vaiheiden kautta kuin value_counts-funktiollakin. Ylimääräinen rivi df.columns.name=” poistaa häiritsevän col_0-otsikon, jonka crosstab tulostaa taulukon vasempaan yläkulmaan.

df2 = pd.crosstab(df['koulutus'], 'lkm')
df2.index = ['peruskoulu', '2.aste', 'korkeakoulu',
   'ylempi korkeakoulu']
df2.columns.name = ''
df2['%'] = df2['lkm'] / df2['lkm'].sum()
df2['kertymä %'] = df2['%'].cumsum()
df2.style.format({'%': '{:,.1%}', 'kertymä %': '{:,.1%}'})
df2

Tässä en pyöristä prosenttilukuja pysyvästi yhden desimaalin tarkkuuteen, vaan ainoastaan näytän ne yhden desimaalin tarkkuudella (df2.style.format({’%’: ’{:,.1%}’, ’kertymä %’: ’{:,.1%}’})).

Tuloste näyttää samalta kuin value_counts-funktiolla laskettu:

lkm3

Luokiteltu frekvenssitaulukko määrälliselle muuttujalle

Datan ikä-muuttuja sisältää työntekijöiden iät vuosina. Frekvenssitaulukon laskemiseksi tarvitsen iän luokittelua sopiviin ikäluokkiin. Tässä kannattaa käytää value_counts-funktiota, koska sille voin antaa luokkarajat bins-argumenttina:

df3 = df['ikä'].value_counts(sort = False, 
   bins = [18,29,39,49,59,69]).to_frame()
df3

Tuloste näyttää seuraavalta:

lkm4

Huomaa, että ensimmäinen bins (18) on ensimmäisen luokan alaraja, joka sisältyy luokkaan, ja muut ovat luokkien ylärajoja, jotka sisältyvät luokkaan. Esimerkiksi 39-vuotias kuuluu luokkaan (29.0, 39.0].

Ikäluokkien frekvenssiprosentit ja kertymäprosentit voin laskea esimerkiksi seuraavasti:

df4 = df['ikä'].value_counts(normalize = True, 
   sort = False, bins = [18,29,39,49,59,69]).to_frame()
df4 = df4.rename(columns = {'ikä': '%'})
df4['kertymä %'] = df4['%'].cumsum()
df4.style.format('{:.1%}')

Value_counts-funktion lisäargumentti normalize = True määrittää laskettavaksi prosentit lukumäärien sijasta.

Tulos näyttää seuraavalta:

lkm5

Monivalinnan valintojen lukumäärät

Monivalintakysymyksessä vastaajalle tarjotaan useita vaihtoehtoja, joista vastaaja saa valita useammankin kuin yhden. Datassa kutakin monivalintakysymyksen vaihtoehtoa vastaa oma sarakkeensa. Jos vaihtoehto on valittu, niin kyseisen vaihtoehdon sarakkeessa on arvo 1, muussa tapauksessa arvo puuttuu (tai on 0). Yhteenvetona yleensä halutaan taulukko, josta selviää kaikkien vaihtoehtojen valintojen lukumäärät.

Esimerkkiaineistossani monivalintaan liittyvät muuttujat ovat työterveyshuolto, lomaosake, kuntosali ja hieroja (työntekijältä on kysytty, mitä työnantajan tarjoamia etuuksia hän on käyttänyt). Yhteenvedon laskemisessa voin käyttää sum-funktiota, joka summaa valinnan merkkinä olevia ykkösiä. Funktiolla sort_values(ascending = False) järjestän tulokset valintojen määrän mukaiseen laskevaan järjestykseen.

df5 = df[['työterv','lomaosa','kuntosa','hieroja']]
   .sum().sort_values(ascending = False).to_frame()
df5 = df5.rename(columns = {0: 'lkm'})
df5.style.format('{:.0f}')

Tulos näyttää seuraavalta:

lkm6

Jos monivalinnalle lasketaan prosentteja, niin yleensä prosentit lasketaan vastaajien kokonaismäärästä. Vastaajien kokonaismäärän saan selville toiminnolla df.shape[0] (vastaavasti df.shape[1] antaisi muuttujien kokonaismäärän). Täydennän prosentit edelliseen taulukkoon seuraavasti:

df5['% vastaajista'] = df5['lkm'] / df.shape[0]
df5.style.format({'% vastaajista':'{:.1%}'})

Tulos näyttää seuraavalta:

lkm7

Ristiintaulukointi

Esimerkiksi koulutuksen ja sukupuolen välisen ristiintaulukoinnin saan pd.crosstab-funktiolla:

df6 = pd.crosstab(df['koulutus'], df['sukup'])
df6.index = ['peruskoulu', '2.aste', 'korkeakoulu',
   'ylempi korkeakoulu']
df6.columns = ['mies','nainen']
df6

Tulos näyttää seuraavalta:

lkm8

Prosenttien vertailu on helpompaa kuin lukumäärien vertaaminen (ainakin tällaisessa tapauksessa, jossa miesten ja naisten kokonaismäärät ovat hyvin erilaiset). Crosstab-funktion lisäargumentilla normalize = ’columns’ lasken sarakeprosentit (prosenttia sukupuolesta). Lisäksi lasken rivisummat lisäargumentilla margins = True.

df7 = pd.crosstab(df['koulutus'], df['sukup'], margins = 
   True, normalize = 'columns')
df7.index = ['peruskoulu', '2.aste', 'korkeakoulu',
   'ylempi korkeakoulu']
df7.columns = ['mies','nainen','yhteensä']
df7.style.format('{:.1%}')

Tulos näyttää seuraavalta:

lkm9

Ikäluokan ja sukupuolen ristiintaulukoimiseksi minun täytyy ensin muodostaa luokiteltu ikä-muuttuja cut-funktiolla.

df['ikäluokka'] = pd.cut(df['ikä'], bins = 
   [18,29,39,49,59,69])
df8 = pd.crosstab(df['ikäluokka'], df['sukup'])
df8.columns = ['mies','nainen']
df8

Tulos näyttää seuraavalta:

lkm10

Huomaa, että ensimmäisen luokan alaraja (18) poikkeaa aiemmin tässä artikkelissa value_counts-funktiolla lasketusta (17.999). Tämän aineiston tapauksessa tällä ei ole väliä, koska aineistossa ei ole yhtään 18 vuotiasta.

Monivalinnan ristiintaulukointi

Monivalinnan valintojen ristiintaulukointi ei onnistu crosstab-funktiolla, koska monivalinnan vaihtoehdoista jokainen muodostaa oman muuttujansa. Ristiintaulukointi onnistuu kuitenkin groupby-funktion avustuksella:

df9 = df.groupby('sukup')['työterv','lomaosa',
   'kuntosa', 'hieroja'].sum()
df9.index = ['mies','nainen']
df9.style.format('{:.0f}')

Tulos näyttää seuraavalta:

lkm11

Prosenttien laskemisessa on luontevinta laskea prosentit sukupuolesta. Tämä vaatii pientä kikkailua: Miesten lukumäärä ja naisten lukumäärä täytyy selvittää value_counts-funktiolla. Tämän jälkeen mies-rivin tiedot täytyy jakaa miesten lukumäärällä ja nainen-rivin tiedot naisten lukumäärällä.

df10 = df.groupby('sukup')['työterv','lomaosa',
   'kuntosa', 'hieroja'].sum()
df10.index = ['mies','nainen']
miehet = df['sukup'].value_counts()[1]
naiset = df['sukup'].value_counts()[2]
df10.loc['mies'] = df10.loc['mies'] / miehet
df10.loc['nainen'] = df10.loc['nainen'] / naiset
df10.style.format('{:.1%}')

Tulostaulukosta selviää muiden muassa että 55,6 % miehistä ja 63,2 % naisista on käyttänyt työterveyshuoltoa.
lkm12

Mielipiteiden koontitaulukko

Jos samalla mielipideasteikolla on kysytty mielipidettä moneen eri asiaan, niin voin koota mielipiteiden frekvenssit koontitaulukkoon. Esimerkkiaineistossa on mitattu tyytyväisyyttä eri asioihin (tyytyväisyys johtoon, tyytyväisyys työtovereihin jne.).  Rakennan ensimmäisen muuttujan mielipiteistä dataframen df11 ja lisään sen jälkeen muiden muuttujien frekvenssit tähän dataframeen.

df11=df['johto'].value_counts(sort = False).to_frame()
df11['työtov'] = df['työtov'].value_counts(sort = False)
df11['työymp'] = df['työymp'].value_counts(sort = False)
df11['palkkat'] = df['palkkat'].value_counts(sort = False)
df11['työtehtäviin'] = df['työteht'].
   value_counts(sort = False)
df11.index = ['erittäin tyytymätön','tyytymätön',
   'ei tyytymätön eikä tyytyväinen', 'tyytyväinen',
   'erittäin tyytyväinen']
df11

Lopputulos näyttää seuraavalta:
lkm13

Huomaa, että kukaan ei ollut erittäin tyytymätön työtovereihin.

Luokiteltu jakauma

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/luokiteltu_jakauma.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein (esimerkiksi sisennykset voivat kopioitua virheellisesti).

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Määrällisen muuttujan arvot kannattaa yleensä luokitella ennen frekvenssien laskemista. Pythonilla tämä sujuu pandas-ohjelmakirjaston cut-funktiolla.

Aluksi avaan Excel-muotoisen datan dataframeen:

import pandas as pd
df=pd.read_excel('http://taanila.fi/data1.xlsx', 
   sheet_name='Data')

Ennen ikä-muuttujan luokittelua tarkastelen ikää tunnuslukujen valossa:

df['ikä'].describe()

pyluoki1

Huomaan muun muassa, että nuorin on 20-vuotias ja vanhin 61-vuotias. Tältä pohjalta voin miettiä sopivia luokkarajoja. Määritän luokkarajat ja luokkien nimet:

bins = [20, 30, 40, 50, 62]
group_names = ['20-29', '30-39', '40-49', '50-']

Tämän jälkeen määritteleen aineistoon uudet muuttujat (sarakkeet), joiden nimiksi annan luokkarajat ja ikäluokka. Jos right=False niin alarajat kuuluvat luokkaan, mutta ylärajat eivät kuulu. Tämän otin huomioon jo luokkarajojen määrittelyssä asettamalla ylimmän rajan 62-vuoden kohdalle.

df['luokkarajat'] = pd.cut(df['ikä'], bins, right = False)
df['ikäluokka'] = pd.cut(df['ikä'], bins, labels = 
   group_names, right = False)

Voin taulukoida luokkarajat ja niiden frekvenssit crosstab-toiminnolla:

pd.crosstab(df['luokkarajat'],'lkm')

pyluoki3

Luokkarajoissa käytetään matematiikasta tuttua merkintätapaa: hakasulkeen vieressä oleva luku kuuluu luokkaan, mutta kaarisulkeen vieressä oleva luku ei kuulu luokkaan.

Seuraavassa taulukoin ikäluokka-muuttujan frekvenssiprosentit, korvaan vasemman ylänurkan col_0-nimen tyhjällä merkkijonolla ja muotoilen prosenttiluvut yhden desimaalin tarkkuuteen:

df1 = pd.crosstab(df['ikäluokka'], 'lkm', normalize = 
   'columns')
df1.columns.name = ''
df1.style.format('{:.1%}')

pyluoki2

Aiemmissa artikkeleissani en ole tainnutkaan määritellä omia funktioita. Nyt korjaan sen puutteen ja määrittelen funktion, joka osaa laskea funktiolle annetuista lähtötiedoista (group) lukumäärän, minimin, maksimin ja keskiarvon. Funktion määrittely aloitetaan sanalla def:

def get_stats(group):
   return {'min': group.min(), 'max': group.max(), 'lkm': 
      group.count(), 'keskiarvo': group.mean()}

Itse määrittelemääni funktiota käyttäen voin kätevästi laskea tunnuslukuja ikäluokittain. Groupby ryhmittelee määrittelemieni ikäluokkien mukaan.

df2 = df['palkka'].groupby(df['ikäluokka']).
   apply(get_stats).unstack()

Unstack() hoitaa tunnuslukujen tulostuksen vierekkäin.

Tunnusluvut tulostuvat aakkosjärjestyksessä keskiarvo, lkm, max, min, jollen vartavasten määritä haluamaani järjestystä. Desimaalien määräksi säädän 0.

df2 = df2[['lkm', 'min', 'max', 'keskiarvo']] 
df2.style.format('{:.0f}')

Lopputulos näyttää seuraavalta:

pyluoki4

Ristiintaulukointi – crosstab()

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/ristiintaulukointi.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein (esimerkiksi sisennykset voivat kopioitua virheellisesti).

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Edellisessä artikkelissani Frekvenssitaulukot Exceliin kerroin miten lasket Pythonilla frekvenssitaulukoita. Tässä artikkelissa jatkan frekvenssien laskemista crosstab-toiminnolla, mutta laajennan tarkastelua frekvenssitaulukoista ristiintaulukointeihin.

Aluksi otan käyttöön pandas-kirjaston ja avaan Excel-muotoisen datan dataframeen.

import pandas as pd
df = pd.read_excel('http://taanila.fi/data1.xlsx', 
   sheet_name = 'Data')

Ristiintaulukointi lukumäärinä

Seuraavaksi lasken ristiintaulukoinnin sukupuolen ja koulutuksen välille. Lisäasetuksella margins=True saan taulukkoon rivi- ja sarakesummat.

df1 = pd.crosstab(df['sukup'], df['koulutus'], margins = True)

Koska aineistossani sukupuolen ja koulutuksen arvot ovat numeroina, niin annan niille selväkieliset nimet:

df1.index = ['Mies', 'Nainen', 'Yhteensä']
df1.columns = ['Peruskoulu', '2. aste', 'Korkeakoulu', 
   'Ylempi korkeakoulu', 'Yhteensä']

Huomaa, että myös rivi- ja sarakesummat täytyy nimetä samalla kuin muuttujien arvot (Yhteensä).

Lopuksi tulostan ristiintaulukoinnin näkyviin komennolla df1.

df13

Ristiintaulukointi prosentteina

Voin laskea ristiintaulukointiin prosentit lisäasetuksella normalize=’columns’ (prosentit sarakkeiden summista):

df2 = pd.crosstab(df['johto'], df['sukup'], 
   margins = True, normalize = 'columns')

Tässäkin annan muuttujan arvoille selväkieliset nimet:

df2.columns = ['Mies', 'Nainen', 'Yhteensä']
df2.index = ['Erittäin tyytymätön', 'Tyytymätön', 
   'Ei tyytymätön eikä tyytyväinen', 'Tyytyväinen', 
   'Erittäin tyytyväinen']

Prosentit näkyvät desimaalimuodossa ellen varta vasten vaihda muotoilua:

df2.style.format('{:.1%}')

Lopputulos näyttää seuraavalta:

df14

Huomaa, että summariviä ei näytetä sarakeprosentteja käytettäessä.

Useampia muuttujia ristiintaulukointiin

Voin korvata muuttujien arvot selväkielisillä nimillä etukäteen itse aineistoon:

df['sukup'].replace([1, 2], ['Mies', 'Nainen'], inplace = True)
df['perhe'].replace([1,2], ['Perheetön', 'Perheellinen'], 
   inplace = True)

Ristiintaulukointiin voin ottaa mukaan useampiakin muuttujia. Seuraavassa otan sarakemuuttujiksi sekä sukupuolen että perheen.

df3 = pd.crosstab(df['johtoon'], [df['sukup'], df['perhe']], 
   margins = True)

Voin nimetä rivi- ja sarakeotsikoita uudelleen:

df3 = df3.rename(columns = {'All': 'Yhteensä'})
df3 = df3.rename(index = {'All': 'Yhteensä'})
df3

df15

Useita ristiintaulukointeja for-silmukalla

Ristiintaulukointeja ei tarvitse laskea yksi kerrallaan. For-silmukalla voin tuottaa kaikki ristiintaulukoinnit kerralla. Harkitsen toki etukäteen mistä kaikesta ristiintaulukointi kannattaa laskea.

Seuraavassa muodostan osa-aineiston niistä muuttujista, joita haluan käyttää ristiintaulukointeihin:

columns = ['sukup', 'perhe', 'koulutus', 'johto', 'työtov',
   'työymp', 'palkkat', 'työteht']
df_osa = pd.DataFrame(df, columns = columns)

Seuraavassa teen kaikki mahdolliset ristiintaulukoinnit sukupuolen kanssa. For-silmukalla voin käydä läpi kaikki df_osa-dataframen muuttujat. Sukupuolta ei voi ristiintaulukoida itsensä kanssa. Tämän takia tarkistan if-lauseella, onko var erisuuri kuin sukupuoli?

for var in df_osa:
   if var != 'sukup':
      df4 = pd.crosstab(df_osa[var], df_osa['sukup'])
      print(df4)

Katsotaan vielä viimeisenä esimerkkinä, miten aineiston kaikki kahden muuttujan väliset ristiintaulukoinnit lasketaan ja kirjoitetaan Exceliin.

writer = pd.ExcelWriter('ristit.xlsx', engine = 'xlsxwriter')
rivi = 0
for var1 in df_osa:
   for var2 in df_osa:
      if var1 != var2:
         df5 = pd.crosstab(df_osa[var1], df_osa[var2])
         df5.to_excel(writer, sheet_name = 'Ristiintaulukoinnit', 
            startrow=rivi)
         rivi = rivi + df5.shape[0] + 2

Tallennan Excel-tiedoston:

writer.save()

Seuraavassa artikkelissani Luokiteltu jakauma luokittelen muuttujan arvot (esimerkiksi palkat palkkaluokiksi) ennen frekvenssien ja tunnuslukujen laskemista.

Frekvenssitaulukot Exceliin

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/frekvenssit.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Edellisessä artikkelissani Tunnuslukuja Exceliin kerroin miten lasket Pythonilla käden käänteessä aineiston tunnusluvut ja korrelaatiot ja tallennat tulokset Exceliin. Frekvenssitaulukoiden laskenta edellyttää enemmän Python-taitoja. Frekvenssitaulukot voin laskea Pythonissa monella eri tavalla. Yrityksen ja erehdyksen kautta olen itse päätynyt seuravassa kuvaamaani tapaan.

Aluksi otan käyttöön pandas-ohjelmakirjaston, avaan aineiston ja alustan ExcelWriterin kirjoittamaan frekvenssit.xlsx -nimiseen tiedostoon. Tämä kaikki on sinulle toivottavasti tuttua jo edellisestä artikkelistani.

import pandas as pd
df = pd.read_excel('http://taanila.fi/data1.xlsx', 
   sheet_name = 'Data')
writer = pd.ExcelWriter('frekvenssit.xlsx', engine = 
   'xlsxwriter')

Seuraavaksi alustan muuttujan rivi arvoksi 0. Tätä muuttujaa tarvitsen osoittaessani mille Excel-taulukon riville tietoja milloinkin kirjoitan. Aloitan arvosta 0, koska Python numeroi rivit alkaen arvosta 0.

rivi = 0

Hyödynnän for-toistorakennetta käydäkseni läpi kaikki aineiston muuttujat (sarakkeet). Tällä tavalla toteutettuna voin käyttää ohjelmaa minkä tahansa aineiston kanssa. For-rakenteen aloitan komennolla:

for var in df:

For-rakenne käy läpi yksi kerrallaan kaikki dataframesta df löytyvät muuttujat (sarakkeet). Muuttujan nimen var sijasta olisin voinut käyttää mitä tahansa keksimääni nimeä. Kaksoispiste on pakollinen ja merkitsee for-rakenteen aloitusta. Kaksoispisteen jälkeen kaikki ohjelmarivit, jotka ovat sisennettyjä, sisältyvät for-rakenteeseen. Monissa muissa ohjelmointikielissä for-rakenne lopetetaan erillisellä komennolla, mutta Pythonissa for-rakenne loppuu kun sisennetyt ohjelmarivit loppuvat. Jos kopioit alla olevat rivit suoraan omaan ohjelmaasi, niin muista sisentää rivit. Minä sisällytin for-rakenteeseen seuraavat ohjelmarivit:

df1 = pd.crosstab(df[var], 'lkm')
df1['prosenttia'] = df1/df1.sum()
df1.loc['Yhteensä'] = df1.sum()
df1.to_excel(writer, sheet_name = 'Frekvenssit', 
   startrow=rivi)
rivi=rivi+df1.shape[0]+2

Käytän crosstab-toimintoa lukumäärien laskemiseen. Crosstab on tässä kätevä, koska sen tuloksena syntyy dataframe, jonka voin helposti kirjoittaa Excel-taulukkoon. Nimeän syntyvät dataframen nimellä df1.

df1[’prosenttia’] = df1/df1.sum() lisää prosentti-sarakkeen. Huomaa, miten kätevästi voin suorittaa laskennan dataframessa.

df1.loc[’Yhteensä’] = df1.sum() lisää df1-dataframen viimeiseksi riviksi summa-rivin.

Seuraavaksi kirjoitan df1-dataframen Frekvenssit-taulukkovälilehdelle alkaen rivi-muuttujan osoittamalta riviltä.

Seuraavaksi kasvatan rivi-muuttujan arvoa, jotta seuraava frekvenssitaulukko kirjoitetaan edellisen alapuolelle siten että väliin jää yksi tyhjä rivi. Dataframen df1 rivimäärän saan selville df1.shape[0] -komennolla. Komento df1.shape[1] kertoisi vastaavasti dataframen df1 sarakkeiden määrän.

Lopuksi vaihdan prosentteja sisältävälle sarakkeelle prosenttimuotoilun. Seuraavia ohjelmarivejä en enää sisennä, koska en halua toistaa niitä jokaisella for-rakenteen toistokerralla:

format = writer.book.add_format({'num_format': '0.0%'})
writer.sheets['Frekvenssit'].set_column('C:C', 
   None, format)

Aluksi lisään writer.book oliolle prosenttimuotoilun, jossa on yksi desimaali. Tämän jälkeen asetan kyseisen muotoilun Frekvenssit-taulukkovälilehden C-sarakkeelle.

Kaiken päätteeksi tietenkin vielä tallennan edellä muodostetun Excelin:

writer.save()

Tämän jälkeen voin avata frekvenssit.xlsx-tiedoston Exceliin ja tutustua aikaansaannokseeeni.

Huomaa, että edellä kuvaamani ohjelma toimii sellaisenaan mille tahansa tilastoaineistolle kunhan vaihdan aineiston avaamiskomennossa tiedostonimen ja polun, josta tiedosto löytyy.

Seuraavassa artikkelissani Ristiintaulukointi lasken crosstab-toiminnolla ristiintaulukointeja.

Tunnuslukuja Exceliin

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/tunnusluvut.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Vaikka käyttäisit Exceliä, niin jotkin asiat sujuvat nopeammin Pythonin kautta. Seuraavassa kerron, miten lasket Pythonilla tunnuslukuja ja korrelaatioita Excel-aineistosta ja kirjoitat tulokset Excel-tiedostoon. Tämä sujuu hämmästyttävän helposti.

Otan käyttöön pandas-ohjelmakirjaston ja avaan Excel-tiedoston dataframeen:

import pandas as pd
df = pd.read_excel('http://taanila.fi/data1.xlsx', 
   sheet_name = 'Data')

Alustan ExcelWriterin kirjoittamaan uuteen Excel-tiedostoon:

writer = pd.ExcelWriter('tunnusluvut.xlsx', engine = 
   'xlsxwriter')

Lasken tunnuslukuja describe()-funktiolla ja kirjoitan tunnusluvut Excel-tiedostoon Tunnusluvut-taulukkovälilehdelle:

df.describe().to_excel(writer, sheet_name = 'Tunnusluvut')

Lasken korrelaatioita corr()-funktiolla ja kirjoitan korrelaatiot Excel-tiedostoon Korrelaatiot-taulukkovälilehdelle:

df.corr().to_excel(writer, sheet_name = 'Korrelaatiot')

Lopuksi tallennan Excel-tiedoston:

writer.save()

Tämän jälkeen voin avata Jupyterin oletuskansiosta tunnusluvut.xlsx-tiedoston Exceliin ja hämmästellä mitä sain aikaan näin vähällä vaivalla.

Pythonilla voin myös muotoilla Exceliin kirjoitettavat tiedot ja jopa luoda Excel-kaavioita. Lisätietoa ExcelWriterista: http://xlsxwriter.readthedocs.io/working_with_pandas.html

Seuraavassa artikkelissani Frekvenssitaulukot Exceliin kerron, miten voit laskea aineiston muuttujista frekvenssitaulukot ja kirjoittaa ne Exceliin.

Ryhmittelyjä groupby-toiminnolla

Tässä artikkelissa lasken dataframen sarakkeille tunnuslukuja toisten muuttujien määräämissä ryhmissä. Ryhmittelyt toteutan groupby()-toiminnolla. Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/df3.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Aloitan avaamalla Excel-muotoisen datan dataframeen:

import pandas as pd
df = pd.read_excel('http://taanila.fi/data1.xlsx',
   sheet_name = 'Data')

Lasken palkalle tunnuslukuja koulutuksen määräämissä ryhmissä uuteen dataframeen (df1):

df1 = df['palkka'].groupby(df['koulutus']).describe()

Tuloste näyttää siistimmältä, jos muotoilen tunnusluvut euron tarkkuudelle jättämällä desimaalit pois:

df1.style.format('{:.0f}')

Muotoilukoodit sijoitan aina aaltosulkeiden {} väliin. Aaltosulkeen jälkeinen kaksoispiste on aloitusmerkki. Pisteen jälkeen ilmoitan desimaalien määrän .0 ja viimeisenä oleva kirjain ilmoittaa tiedon tyypin (f = float eli liukuluku).

Tuloksena saan seuraavan näköisen taulukon:

df8

Seuraavaksi lasken palkkakeskiarvoja sukupuolen ja koulutuksen määräämissä ryhmissä:

df2 = df['palkka'].groupby([df['sukup'], df['koulutus']]).
   mean().unstack()
df2.style.format('{:.0f}')

Huomaa, miten kahden tai useamman ryhmittelevän muuttujan lista täytyy laittaa hakasulkeiden sisään. Funktio unstack() siirtää jälkimmäisen ryhmittelevän muuttujan (koulutus) ryhmät sarakkeisiin:

df9

Koulutuksen 4 (ylempi korkeakoulu) omaavia naisia ei aineistossa ole (nan).

Seuraavassa lasken keskiarvot muuttujille ’palkka’ ja ’tyytyväisyys palkkaan’.

df3 = df.groupby(['koulutus', 'sukup']).agg({'palkka':'mean', 
   'palkkat':'mean'})
df3.style.format('{:.2f}')

Aiemmissa esimerkeissä rajasin heti alussa, että tunnuslukuja lasketaan tietylle muuttujalle (esimerkiksi df[’palkka’]). Tällöin ryhmittelevien muuttujien kohdalla tarvitaan tieto dataframesta (esimerkiksi df[’koulutus’]. Tässä esimerkissä ryhmittelevien muuttujien kohdalla ei tarvitse määritellä erikseen dataframea, koska df on jo alussa määritelty ilman rajauksia (df.groupby…).

Funktiolla agg() voin määritellä täsmällisesti mille muuttujille lasken mitäkin tunnuslukuja. Tulos näyttää seuraavalta:

df10

Seuraavassa lasken tyytyväisyyskeskiarvoja sukupuolen ja koulutuksen mukaan ryhmiteltyinä:

df4 = df.groupby(['sukup', 'koulutus'])[['johtoon','työtov']].
   mean()
df4.style.format('{:.2f}')

Huomaa, miten kahden tai useamman muuttujan lista täytyy laittaa hakasulkeiden sisään. Tulos näyttää seuraavalta:

df11

Seuraavassa lasken palkkakeskiarvot sukupuolittain niille, jotka ovat käyttäneet hierojaa:

df[df['hieroja'] == 1].groupby('sukup')['palkka'].mean()

Seuraavassa lasken palkan tunnusluvut sukupuolittain niille, jotka ovat käyttäneet hierojaa ja työterveyshuoltoa:

df[(df['hieroja'] == 1) & (df['työterv'] == 1)].
   groupby('sukup')['palkka'].describe()

Tulos näyttää seuraavalta:

df12

Seuraavassa artikkelissani Tunnuslukuja Exceliin lasken tilastollisia tunnuslukuja ja tallennan tulokset Excel-tiedostoon.