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://www.elisanet.fi/taanila/data1.xlsx', 
   sheetname = '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öterveyshuolto','lomaosake','kuntosali','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['sukupuoli'])
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['sukupuoli'], 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['sukupuoli'])
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('sukupuoli')['työterveyshuolto','lomaosake','kuntosali',
   '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('sukupuoli')['työterveyshuolto','lomaosake','kuntosali',
   'hieroja'].sum()
df10.index = ['mies','nainen']
miehet = df['sukupuoli'].value_counts()[1]
naiset = df['sukupuoli'].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.). Lyhennän ensin muuttujien nimet df.rename-funktiolla (ei näin pitkiä muuttujan nimiä olisi kannattanut lainkaan käyttää!). Lisäargumentti inplace = True takaa sen, että dataframesta ei muodosteta kopiota, vaan uudet lyhennetyt nimet tallennetaan alkuperäisten tilalle.Tämän jälkeen rakennan ensimmäisen muuttujan mielipiteistä dataframen df11 ja lisään sen jälkeen muiden muuttujien frekvenssit tähän dataframeen.

df.rename(columns = {'tyytyväisyys johtoon': 'johtoon', 
  'tyytyväisyys työtovereihin': 'työtovereihin',
  'tyytyväisyys työympäristöön': 'työympäristöön', 
  'tyytyväisyys palkkaan': 'palkkaan',
  'tyytyväisyys työtehtäviin': 'työtehtäviin'}, inplace = True)
df11=df['johtoon'].value_counts(sort = False).to_frame()
df11['työtovereihin'] = df['työtovereihin'].value_counts(sort = False)
df11['työympäristöön'] = df['työympäristöön'].value_counts(sort = False)
df11['palkkaan'] = df['palkkaan'].value_counts(sort = False)
df11['työtehtäviin'] = df['työtehtäviin'].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

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).

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://www.elisanet.fi/taanila/data1.xlsx', sheetname='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).

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://www.elisanet.fi/taanila/data1.xlsx', sheetname = '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['sukupuoli'], 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['tyytyväisyys johtoon'], df['sukupuoli'], 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['sukupuoli'].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['tyytyväisyys johtoon'], [df['sukupuoli'], 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 = ['sukupuoli', 'perhe', 'koulutus', 'tyytyväisyys johtoon', 'tyytyväisyys työtovereihin',
 'tyytyväisyys työympäristöön', 'tyytyväisyys palkkaan', 'tyytyväisyys työtehtäviin']
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 != 'sukupuoli':
      df4 = pd.crosstab(df_osa[var], df_osa['sukupuoli'])
      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 (esimerkiksi sisennykset voivat kopioitua virheellisesti).

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, mutta helpompia ja tehokkaampiakin tapoja voi olla olemassa.

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://www.elisanet.fi/taanila/data1.xlsx', sheetname = '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

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://www.elisanet.fi/taanila/data1.xlsx', sheetname = '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

Aloitan avaamalla Excel-muotoisen datan dataframeen:

import pandas as pd
df = pd.read_excel('http://www.elisanet.fi/taanila/data1.xlsx',sheetname = '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['sukupuoli'], 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', 'sukupuoli']).agg({'palkka':'mean', 'tyytyväisyys palkkaan':'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(['sukupuoli', 'koulutus'])[['tyytyväisyys johtoon','tyytyväisyys työtovereihin']].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('sukupuoli')['palkka'].mean()

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

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

Tulos näyttää seuraavalta:

df12

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

Temppuja dataframe-tietorakenteella

Tässä artikkelissa avaan Excel-muotoisen datan dataframeen ja teen sen jälkeen erilaisia suodatuksia ja laskentaa datalle. Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

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

Aluksi otan käyttöön pandas-kirjaston ja avaan Excel-datan:

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

Datan alustavaa tarkastelua

Komennolla df.shape minulle selviää datan koko (82 riviä ja 16 saraketta).

Komennolla df.count() minulle selviää arvojen lukumäärä kussakin sarakkeessa:

df5

Joidenkin muuttujien kohdalla on puuttuvia tietoja ja sen seurauksena arvojen määrä on pienempi kuin 82. Työterveyshuollon, lomaosakkeen, kuntosalin ja hierojan kohdalla on arvo vain niillä vastaajilla, joka ovat käyttäneet kyseisiä työnantajan tarjoamia palveluita.

Komennolla df.sort_values(’palkka’) voin näyttää data tiedot palkan mukaisessa järjestyksessä.

Suodatuksia

Dataa suodattamalla voin muodostaa erilaisia osadatoja. Voin esimerkiksi muodostaa osadatan niistä riveistä, joilla vastaaja on tyytymätön johtoon (tyytyväisyys johtoon on pienempi kuin 3). Funktiolla head() voin tulostaa muodostetun osadatan 5 ensimmäistä riviä:

df_filtered1 = df[df['tyytyväisyys johtoon'] < 3]
df_filtered1.head()

Seuraavassa suodattan mukaan ne, joilla on palvelusvuosia alle 2 ja jotka ovat erittäin tyytymättömiä johtoon:

df_filtered2 = df[(df['palvelusvuodet'] < 2) & (df['tyytyväisyys johtoon'] == 1)]
df_filtered2.head()

Suodatusehdoissa voit käyttää Pythonin loogisia operaattoreita (and &, or | ).

Laskentaa

Voin laskea uusia sarakkeita olemassa olevien sarakkeiden perusteella. Esimerkkinä lisään sarakkeen bonus, johon lasken lisäbonuksen (5 % palkasta) ja sarakkeen yhteensä, johon lasken palkan ja lisäbonuksen yhteensä:

df['bonus'] = df['palkka'] * 0.05
df['yhteensä'] = df['palkka'] + df['bonus']
df[['palkka', 'bonus', 'yhteensä']].head()

Viimeisessä komennossa määritän näytettäväksi dataframesta vain sarakkeet palkka, bonus ja yhteensä, bonus ja yhteensä. Huomaa, miten lista täytyy sijoittaa hakasulkeiden sisään.

Lisään vielä 3 prosentin bonuksen alle 3000 euron palkoille:

df['lisäbonus'] = (df['palkka'] < 3000) * 0.03 * df['palkka']
df[['palkka', 'bonus', 'yhteensä', 'lisäbonus']]

Testi (df[’palkka’] < 3000) antaa tulokseksi 1 (True), jos ehto on totta. Muussa tapauksessa ehto antaa tulokseksi 0 (False). Datan alkuosa näyttää seuraavalta:

df6

Frekvenssejä ja tilastollisia tunnuslukuja

Value_counts-funktio laskee muuttujan ainutkertaisten arvojen frekvenssit. Esimerkiksi

df['koulutus'].value_counts()

antaa tulokseksi

df7

Jos haluan järjestää tulokset frekvenssien mukaisen järjestyksen sijasta koulutuksen mukaiseen järjestukseen, niin käytän komentoa

df['koulutus'].value_counts(sort = False)

Describe-funktio laskee dataframen jokaiselle muuttujalle tunnusluvut: count (lukumäärä), mean (keskiarvo), std (keskihajonta; jakajana n-1), min (pienin), 25 % (alaneljännes), 50 % (mediaani), 75 % (yläneljännes), max (suurin).

Voit siis laskea kyseiset tunnusluvut komennolla df.describe().

Komennolla df.corr() lasket muuttujien väliset korrelaatiokertoimet.

Lopuksi lasken vielä keskiarvopalkan niille, jotka ovat tyytyväisiä palkkaan (tyytyväisyys palkaan on 4 tai 5):

df['palkka'][df['tyytyväisyys palkkaan'] >= 4].mean()

Seuraavassa artikkelissani Ryhmittelyjä groupby-toiminnolla teen monenlaisia ryhmittelyjä groupby()-toiminnolla.