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.

Mainokset

Yksi ajatus artikkelista “Lukumääriä ja prosentteja”

Kommentit on suljettu.