Data-analytiikkaa Pythonilla

Data-analytiikka antaa vastauksia kysymyksiin

Data-analytiikka on tavoitteellista toimintaa: tavoitteena on vastata kysymyksiin. Data-analytiikan avulla vastataan monenlaisiin kysymyksiin:

  • Minkälainen ikäjakauma asiakkaillamme on?
  • Mihin toimintamme osa-alueisiin asiakkaamme ovat tyytymättömiä?
  • Onko asiakkaan iällä yhteyttä asiakastyytyväisyyteen?
  • Miten yrityksen työilmapiiri on muuttunut viimevuodesta?
  • Ketkä asiakkaistamme ovat vaarassa siirtyä kilpailijalle?
  • Keille tuotteen markkinointikampanja kannattaa suunnata?
  • Mikä mainosvaihtoehdoista tehoaa parhaiten kohderyhmään?
  • Mitä oheistuotteita verkkokaupasta ostaneella kannattaa tarjota?
  • Mikä on tuotteen ennustettu kysyntä ensi kuussa?
  • Liittyykö vakuutuskorvaushakemukseen vakuutuspetos?
  • Millä todennäköisyydellä laina-asiakas ei pysty maksamaan lainaansa takaisin?

Data

Tavoitteen (kysymykset, joihin halutaan vastata) asettamisen jälkeen pitää selvittää minkälaista dataa tarvitaan. Data voi olla esimerkiksi:

  • Yrityksen tietokannoista löytyvää dataa (esimerkiksi CRM- ja ERP-järjestelmistä).
  • Erilaisten tiedontuottajien tarjoamaa ilmaista tai maksullista dataa.
  • Varta vasten kyselytutkimuksella tai kokeellisella tutkimuksella kerättyä dataa.

Data-analytiikan tarpeisiin datan täytyy olla taulukkomuotoista. Yleisiä data-analytiikkaan sopivia tiedostomuotoja ovat pilkkueroteltu tekstimuoto (.csv) ja Excel-muoto (.xls tai .xlsx). Tietokannoista data haetaan kyselyiden avulla. Nettikyselyohjelmista datan saa yleensä ulos pilkkuerotellussa tekstimuodossa tai Excel-muodossa.

Kun sopiva data on olemassa, niin datasta saadaan vastauksia kysymyksiin seuraavien vaiheiden kautta:

  • Datan valmistelu.
  • Datan kuvailu.
  • Tilastollinen merkitsevyys: Tämä vaihe tulee kyseeseen, jos data pohjautuu isommasta joukosta poimittuun otokseen. Tilastollinen merkitsevyys kertoo, millä varmuudella otoksessa havaittuja eroja ja riippuvuuksia voidaan yleistää isompaan joukkoon.
  • Koneoppiminen ja ennakoiva analytiikka.

Datan valmistelu

Datan valmistelu on yleensä data-analytiikan aikaa vievin vaihe. Ensimmäiseksi kannattaa varmistaa datan taulukkomuotoisuus: muuttujien nimet / kenttien nimet / sarakeotsikot ovat ensimmäisellä rivillä, datassa ei ole tarpeettomia tyhjiä rivejä tai sarakkeita, kuhunkin tilastoyksikköön/havaintoyksikköön liittyvät tiedot ovat yhdellä rivillä. Datan valmistelu voi sisältää muiden muassa seuraavia:

  • Muuttujien uudelleen nimeäminen: jatkotoimet sujuvat sutjakkaammin, jos nimet ovat lyhyitä ja helposti tunnistettavia.
  • Desimaalipilkkujen tarkistaminen: vaikka Suomessa desimaalipilkkuna käytetään pilkkua, niin Pythonissa kannattaa käyttää pistettä.
  • Päivämäärien muuntaminen päivämääriksi tunnistettavaan muotoon.
  • Mittayksiköiden tarkistaminen ja tarvittavien muunnosten tekeminen.
  • Puuttuvien arvojen käsittely: poistetaanko puuttuvia arvoja sisältävät rivit, korvataanko puuttuvat arvot jollain, miten puuttuvia arvoja merkitään?
  • Uusien muuttujien laskeminen: esimerkiksi summamuuttuja useasta mielipidemuuttujasta, tilauksen hinta tilausmäärän ja yksikköhinnan avulla jne.
  • Arvojen luokittelu ja uudelleenkoodaaminen: esimerkiksi ikäluokat iän arvoista.

Datan kuvailu

Datan kuvailu voi sisältää seuraavia:

  • Lukumäärä- ja prosenttiyhteenvetojen laskeminen (frekvenssitaulukot).
  • Tilastollisten tunnuslukujen laskeminen (keskiarvo, keskihajonta, viiden luvun yhteenveto).
  • Riippuvuuksien tarkastelu (ristiintaulukoinnit, korrelaatiot).
  • Prosenttimuutosten laskeminen aikasarjoille.
  • Aikasarjojen tarkastelu viivakuvioina.
  • Liukuvien keskiarvojen esittäminen aikasarjojen yhteydessä.

Kuvailun tuloksia kannattaa visualisoida ja havainnollistaa hyvin viimeistellyillä taulukoilla ja kuvioilla.

Tilastollinen merkitsevyys

Jos käytetty data on otos isommasta perusjoukosta, niin tulokset kuvaavat otosta. Jos tarkoituksena on arvioida koko perusjoukkoa, niin otoksessa havaittujen erojen ja riippuvuuksien tilastollinen merkitsevyys kertoo, millä varmuudella eroja ja merkitsevyyksiä voidaan yleistää perusjoukkoon.

Koneoppiminen ja ennakoiva analytiikka

Koneoppimisen malleilla voidaan luokitella (asiakkaat luottoriski-asiakkaisiin ja muihin, vakuutuskorvaushakemukset selviin tapauksiin ja petokselta haiskahtaviin jne.) ja ennakoida (tulevaa kysyntää jne.). Koneoppiminen perustuu siihen, että kone oppii käytettävän mallin parametrit olemassa olevasta datasta ja tämän jälkeen mallia voidaan soveltaa uuteen dataan.

Koneoppimisalgoritmit voidaan luokitella  seuraavasti (suomennokset eivät ole vakiintuneita):

  • Supervised learning (ohjattu oppiminen): Algoritmi opetetaan opetusdatalla (training data). Esimerkiksi roskapostisuodatin opetetaan sähköpostidatalla, jossa on erilaisia tietoja kustakin sähköpostiviestistä sekä tieto siitä oliko sähköpostiviesti roskapostia. Tämän datan perusteella muodostuu malli, jota käyttäen tulevista sähköpostiviesteistä voidaan tunnistaa roskapostiviestit.
  • Unsupervised learning (ohjaamaton oppiminen): Esimerkiksi asiakkaiden jakaminen asiakassegmentteihin.
  • Reinforcement learning (vahvistusoppiminen): Algoritmi suorittaa toimia ja saa niistä palautetta palkkioiden ja rangaistuksen muodoissa. Algoritmi oppii saamistaan palkkioista ja rangaistuksista. Vahvistettua oppimista käytetään esimerkiksi itseajavissa autoissa.

Seuraavassa jaotellaan ohjattu ja ohjaamaton oppiminen edelleen alatyyppeihin:

kone1

Ohjattu oppiminen

Label tarkoittaa ennakoitavaa asiaa (selitettävää muuttujaa).

Diskreetti label

Jos ennakoitava asia on diskreetti (epäjatkuva), niin kyseeseen tulevat luokittelua suorittavat algoritmit, esimerkiksi logistinen regressio.

Esimerkkejä, joissa on diskreetti label:

  • Roskapostisuodatin: Label on tieto siitä, onko sähköpostiviesti roskapostia vai ei?
  • Lääketieteellinen diagnoosi: Label on tieto siitä, onko tutkitulla potilaalla tietty sairaus vai ei?
  • Vakuutuspetosten tunnistaminen: Label on tieto siitä, liittyykö korvaushakemukseen petos vai ei?

Jatkuva label

Jos ennakoitava asia on jatkuva, niin kyseeseen tulevat esimerkiksi regressiomallit ja aikasarjaennustamisen menetelmät. Esimerkkejä, joissa on jatkuva label:

  • Osakehuoneiston hinnan ennustaminen: Label on asunnon hinta.
  • Kysynnän ennustaminen aikaisemman kysynnän perusteella: Label on kysyntä.

Ohjaamaton oppiminen

Ohjaamattomassa oppimisessa ei ole labelia (selitettävää/ennakoitavaa muuttujaa). Ohjaamattoman oppimisen algoritmi muodostaa mallin suoraan datasta. Esimerkkinä asiakassegmenttien määrittäminen asiakasdatan pohjalta. Paljon käytetty algoritmi on k-means clustering.

Jos datassa on paljon muuttujia, jotka mittaavat osittain samoja asioita, niin dimensionality reduction -tyyppisillä algoritmeilla voidaan pienentää muuttujien määrää yhdistämällä niitä kimpuiksi. Tunnetuin algoritmi tähän tarkoitukseen on pääkomponenttianalyysi.

Data-analytiikkaa Pythonilla

Jos aiot käyttää Pythonia data-analytiikassa, niin kannattaa aloittaa asentamalla Anaconda.

Mainokset

Aikasarjat

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

https://github.com/taanila/tilastoapu/blob/master/aikasarjat.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.

Tämän artikkelin esimerkeissä käytän osoitteesta

http://www.nasdaqomxnordic.com/osakkeet/historiallisetkurssitiedot

noutamiani csv-muotoisia historiatietoja Elisan ja Telian osakkeiden kurssikehityksestä:

Ohjelmakirjastojen tuonti

Aikasarjojen käsittelyyn ja kuvamiseen tarvitsen ainakin pandas ja matplotlib.pyplot kirjastoja:

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

Aikasarjojen avaaminen dataframeen

Jos aikasarjan sisältävä tekstimuotoinen tiedosto on tallennettu työasemalleni, niin voin kurkistaa sen sisään esimerkiksi seuraavasti:

aikas1

Yllä olevan perusteella huomasin että nasdaqomxnordicin sivuilta noudetut historialliset kurssitiedot on tallennettu seuraavasti:

  • erottimena puolipiste
  • desimaalipisteenä pilkku
  • ensimmäinen rivi ei sisällä varsinaista dataa.

Edellä olevan perusteella päädyin avaamaan aikasarjan dataframeen seuraavila parametreillä:

elisa = pd.read_csv('ELISA-2016-01-01-2018-10-19.csv', sep = ';', 
   decimal = ',', skiprows = 1, usecols = 
   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
telia = pd.read_csv('TELIA1-2016-01-01-2018-10-19.csv', sep = ';', 
   decimal = ',', skiprows = 1, usecols = 
   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

Aikasarjasta tulee kunnon aikasarja sijoittamalla aikaleimat dataframen rivi-indeksiksi. Varmistan pd.to_datetime()-funktiolla, että aikaleimat tunnistetaan päivämääriksi. Lisäksi järjestän indeksin päivämäärien mukaan vanhimmasta uusimpaan:

elisa.index = pd.to_datetime(elisa['Date'])
elisa.sort_index(inplace = True)

telia.index = pd.to_datetime(telia['Date'])
telia.sort_index(inplace = True)

Viivakaavio

Viivakaavion piirtäminen esimerkiksi päätöshinnoista (Closing price) onnistuu helposti:

elisa['Closing price'].plot()

aikas2

Voin tarkastella kuviossa vain rajattua osaa aikasarjasta, esimerkiksi syyskuusta 2018 alkaen:

elisa['Closing price']['2018-09':].plot()

aikas3

Liukuva keskiarvo

Liukuvan keskiarvon voin laskea rolling()-funktiolla:

elisa['Closing price'].plot()
elisa['Closing price'].rolling(200).mean().plot()

aikas4

Halutessani voin tallentaa liukuvat keskiarvot dataframeen:

elisa['rolling200'] = elisa['Closing price'].rolling(200).mean()

Voin korvata mean()-funktion ja laskea liukuvasti myös muita tilastollisia tunnuslukuja.

Kaksi aikasarjaa samaan kaavioon

Esimerkkiaikasarjojeni arvot ovat eri suuruusluokkaa (Elisan osake kymmeniä euroja, Telian osake muutamia euroja). Jos haluan esittää tällaiset aikasarjat samassa kaaviossa ”päällekkäin”, niin tarvitsen kummallekin sarjalle oman arvoakselin.

Seuraavassa lisään ensin kaavion, jonka koon määritän oletusta suuremmaksi. Ensimmäisen sarjan väriksi valitsen käytössä olevan teeman ensimmäisen värin (C0). Toiselle sarjalle määritän oman akselin (ax2 = ax1.twinx()) ja väriksi valitsen käytössä olevan teeman toisen värin (C1).

fig, ax1 = plt.subplots(figsize = (10, 6))

color = 'C0'
#ax1.set_xlabel('Aika')
ax1.set_ylabel('Elisa', color=color)
ax1.plot(elisa['Closing price'], color=color)
ax1.tick_params(axis='y', labelcolor=color)

ax2 = ax1.twinx()
color = 'C1'
ax2.set_ylabel('Telia', color=color)
ax2.plot(telia['Closing price'], color=color)
ax2.tick_params(axis='y', labelcolor=color)

aikas5

Muutosprosentit

Muutosprosentit lasken kätevästi:

elisa['Elisa_Change'] = elisa['Closing price'].pct_change()
telia['Telia_Change'] = telia['Closing price'].pct_change()

Seuraavassa teen muutosprosenteista oman dataframen:

muutokset = pd.concat([elisa['Elisa_Change'], 
   telia['Telia_Change']], axis=1)
muutokset.head()

aikas6

Seuraavassa piirrän muutokset syyskuusta alkaen viivakaavioon:

muutokset['2018-09':].plot()

aikas7

Liukuva korrelaatio

Esimerkiksi 125 päivän liukuva korrelaatio kertoo viimeisimmän 125 päivän korrelaation kahden aikasarjan arvojen välillä. Liukuvaa korrelaatiota laskettaessa on syytä käyttää prosenttimuutoksia.

korrelaatio=muutokset['Elisa_Change'].rolling(125, 
   min_periods=30).corr(muutokset['Telia_Change'])
korrelaatio.plot()

aikas8

Yllä Elisan ja Soneran prosenttimuutokset ovat aluksi korreloineet voimakkaast, mutta vuoden 2018 syyskuun vaiheilla korrelaatiokerroin on pudonnut lähelle arvoa 0,2.

Tarkastelu viikonpäivittäin

Jos haluan tutkia, onko tuottoprosenteissa eroa eri viikonpäivien välillä, niin tallennan ensin viikonpäivät dataframeen. Sen jälkeen voin laskea tuottoprosentin tunnusluvut eri viikonpäiville (0 = maanantai):

muutokset['Weekday'] = muutokset.index.weekday
muutokset['Elisa_Change'].groupby(muutokset['Weekday']).describe()

aikas9

Lisätietoa

Jos olet kiinnostunut aikasarjaennustamisesta, niin tutustu myös artikkeleihin

 

 

 

Olennaiset taidot kuvailevaan analyysiin

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

https://github.com/taanila/tilastoapu/blob/master/olennaiset.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.

Tämän artikkelin esimerkeissä käytän dataa http://taanila.fi/data1.xlsx

Johdanto

Kuvailevan analyysiin voin vaiheistaa esimerkiksi seuraavasti:

  • Ohjelmakirjastojen tuonti
  • Datojen avaaminen
  • Dataan tutustuminen
  • Datan valmistelu
  • Datan kuvailu (lukumäärät, prosentit, tunnusluvut, korrelaatiot)
  • Tulosten viimeistely
  • Tulosten siirtäminen raporttiin

Käyn läpi kuvailevan analyysin vaiheiden olennaisimmat taidot omissa luvuissaan. Tulosten viimeistelylle en varaa omaa lukuaan, vaan käsittelen aihetta sekä Datan kuvailu -luvussa että Tulosten siirtäminen raporttiin -luvussa.

Ohjelmakirjastojen tuonti

Olennaiset kirjastot datojen analysointiin ovat numpy, pandas ja matplotlib.pyplot. Vakiintuneen tavan mukaisesti käytän niille lyhenteitä np, pd ja plt.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

%matplotlib inline

Viimeisen rivin (%matplotlib inline) ansiosta kuviot tulostuvat Jupyter-notebookiin ilman erillistä tulostuskomentoa.

Datojen avaaminen

Datan luen dataframe-tyyppisen muuttujan arvoksi. Jos data-tiedosto on netissä, niin käytän nettiosoitetta; jos data on työasemalla, niin käytän tiedostopolkua. Jos data on samassa kansiossa ohjelmakoodin kanssa, niin pelkkä tiedostonimi riittää.

Tekstitiedosto: pd.read_csv()

Jos data on csv-muotoisena tekstiedostona, niin avaaminen sujuu pd.read_csv() -funktiolla:

df = pd.read_csv('http://taanila.fi/data1.csv', 
    sep = ';', decimal = ',')

Suomalaisilla asetuksilla tallennetuissa csv-datoissa on erottimena puolipiste ja desimaalierottimena pilkku. Jos erottimena on pilkku ja desimaalierottimena piste, niin sep– ja decimal-parametreja ei tarvitse erikseen määrittää.

Lisätietoa pd.read_csv -funktion lisäparametreista:

https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html

Excel-tiedosto: pd.read_excel()

Jos data on Excel-muodossa kuten tämän artikkelin esimerkkidata, niin avaaminen sujuu pd.read_excel() -funktiolla:

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

Vanhemmissa pandas-versioissa sheet_name sijasta täytyy käyttää sheetname. Taulukkovälilehteä (sheet_name) ei tarvitse määritellä, jos data on ensimmäisellä taulukkovälilehdellä. Jos datan yläpuolella on dataan kuulumattomia rivejä, niin tarvitaan lisäparametria skiprows.

Lisätietoa pd.read_excel -funktion lisäparametreista:

https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_excel.html

Tietokanta tai html: pd.read_sql(), pd.read_html()

Tietokantoja varten pandas sisältää pd.read_sql() -funktion ja html-sivuilta lukemista varten pd.read_html() -funktion.

Dataan tutustuminen

Datan voin tulostaa näytölle suoraan dataframen nimellä, esimerkiksi df. Pitkistä datoista vain datan alku- ja loppuosa tulostuu näkyviin.

head(), tail()

Ensimmäiset viisi riviä voin tulostaa funktiolla df.head() ja viisi viimeistä riviä funktiolla df.tail(). Jos haluan nähdä enemmän kuin 5 riviä, niin kirjoitan haluamani rivimäärän sulkeiden sisään, esimerkiksi df.head(10).

columns

Datan sarakkeiden (muuttujien) nimet näen funktiolla df.columns.

count()

Sarakkeiden ei-tyhjien arvojen lukumäärät näen funktiolla df.count().

olennaiset1

np.unique()

Sarakkeiden ainutkertaiset arvot näen numpyn unique-funktiolla:

for var in df:
   print(var, np.unique(df[var]))

Esimerkkiaineistolleni tulosteen alku näyttää seuraavalta:

olennaiset2

Datan valmistelu

replace()

Voin koodata muuttujan arvot uudella tavalla replace()-funktiolla. Esimerkiksi seuraavassa luon uuden sukup2-muuttujan, jossa sukupuolet ovat tekstimuodossa:

df['sukup2']=df['sukup'].replace({1 : 'Mies', 2 : 'Nainen'})
df.head(6)

pd.cut()

Voin luokitella määrällisen muuttujan arvot pd.cut()-funktiolla. Seuraavassa on ensiksi määritelty luokkarajat bins-listaan.

bins = [18, 28, 38, 48, 58, 68]
df['ikä2'] = pd.cut(df['ikä'], bins = bins)
df.head()

rename()

Voin vaihtaa muuttujien nimiä:

df.rename(columns = {'sukup2': 'sukup_teksti', 
   'ikä2': 'ikäluokka'}, inplace = True)
df.head()

Muuttujien tekstimuotoiset arvot listana

Analyysit sujuvat parhaiten, jos tiedot ovat numeromuodossa. Analyysin tuloksiin haluan kategoristen ja mielipidemuuttujien tekstimuotoiset arvot. Tätä ajatellen määrittelen listat muuttujien tekstiarvoille:

koulutus = ['Peruskoulu', '2. aste', 'Korkeakoulu', 'Ylempi korkeakoulu']
perhe = ['Perheetön', 'Perheellinen']
sukup = ['Mies', 'Nainen']
tyytyväisyys = ['Erittäin tyytymätön', 'Jokseenkin tyytymätön', 
   'Ei tyytymätön eikä tyytyväinen', 'Jokseenkin tyytyväinen', 
   'Erittäin tyytyväinen']

Datan kuvailu

Yhteenveto lukumäärinä: pd.crosstab()

Lukumäärä-yhteenvedot saan pd.crosstab()-funktiolla. Funktion toinen parametri (’n’) määrittää otsikon lukumäärille.

df1 = pd.crosstab(df['koulutus'], 'n')
df1.index = koulutus
df1.columns.name = ''
df1

Lisään koulutuksen tekstimuotoiset arvot df1-dataframen rivi-indeksiksi (df1.index = koulutus). Rivi df1.columns.name = ” (viimeisenä on kaksi hipsua) korvaa df1-dataframen vasemman yläkulman otsikon tyhjällä.

olennaiset3

Lisään lukumäärien viereen %-sarakkeen:

df1['%'] = df1/df1.sum()
df1.style.format({'%': '{:.1%}'})

Jos haluan tulostaulukot viimeistellyssä muodossa, niin tarvitsen style.format()-funktiota. Edellä määritän df1-dataframen %-sarakkeelle prosenttimuotoilun yhdellä desimaalilla.

olennaiset4

Tästä ei ole pitkä matka pylväskuvioon. df1-dataframen %-sarakkeesta saan pylväskuvion plot.barh()-funktiolla. Värimäärityksen (color = ’C0’) teen, jotta kaikki pylväät ovat saman värisiä (väri C0 tarkoittaa käytössä olevan teeman ensimmäistä väriä).

ax = df1['%'].plot.barh(color = 'C0')
vals = ax.get_xticks()
ax.set_xticklabels(['{:.0%}'.format(x) for x in vals])

olennaiset5

Arvoakselin asteikon esittämiseksi prosentteina luen ensin ax-muuttujasta asteikon arvot ja sen jälkeen muotoilen jokaisen arvon prosenteiksi ilman desimaaleja.

Monivalinta: count()

Monivalintakysymys on kysymys, jonka vaihtoehdoista vastaaja voi valita useampiakin. Monivalinnan jokainen vaihtoehto tallennataan dataan omana sarakkeenaan. Valintaa merkitään ykkösellä (tai joskus jollain muulla numerolla) ja valitsematta jättämistä tyhjällä.

Esimerkkiaineistossani työterv, lomaosa, kuntosa ja hieroja ovat olleet vaihtoehtoja, joista vastaajat ovat valinneet ne työantajan tarjoamat etuisuudet, joita ovat hyödyntäneet. Valintojen lukumäärät voin laskea count()-funktiolla:

df2=df[['työterv', 'lomaosa', 'kuntosa', 'hieroja']].count()
df2 = df2.to_frame('n').sort_values(by = 'n', ascending = False)
df2.style.format('{:.0f}')

Laskemisen tulos ei ole dataframe, vaan series. Toisella rivillä vaihdan series-tyyppisen tuloksen dataframeksi, määritän valintojen lukumääräsarakkeen nimeksi n ja järjestän dataframen valintojen mukaiseen laskevaan järjestykseen.

Muotoilen valintojen lukumäärät ilman desimaaleja esitetyiksi liukuluvuiksi (liukuluvun symboli on f; liukuluku tarkoittaa käytännössä desimaalilukua).

olennaiset6

Tämän voin esittää pylväskaaviona:

df2.sort_values(by = 'n').plot.barh(legend = False, color = 'C0')
plt.xlabel('Käyttäjien lukumäärä')

Ilman legend = False parametria kuvioon tulee tarpeeton pylväsvärin selite. Arvoakselille lisäsin otsikoksi ’Käyttäjien lukumäärä’.

olennaiset7

Jos haluan ristiintaulukoida monivalinnan valintojen lukumäärät sukupuolen kanssa, niin voin hyödyntää groupby()-funktiota:

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

Rivi-indeksin tekstimuotoiset arvot (mies, nainen) saan aiemmin määritellystä sukup-listasta. Lukumäärät esitän liukulukuna ilman desimaaleja.

olennaiset8

Tästä syntyy vaivatta pylväskuvio:

df3.plot.barh()
plt.xlabel('Käyttäjien lukumäärä')

olennaiset9

Ristiintaulukointi: pd.crosstab()

Edellä tarkastelin monivalinnan valintojen lukumäärän ristiintaulukointia sukupuolen kanssa. Muissa tapauksissa ristiintaulukointi sujuu pd.crosstab()-funktiolla. Ensimmäisen argumentin muuttuja sijoittuu ristiintaulukoinnin riveille ja toisen argumentin muuttuja sarakkeisin. Lisäparametrillä normalize voin määrittää laskettavaksi prosentit lukumäärien sijaan. Seuraavassa lasken prosentit sarakkeista eli sukupuolesta (columns):

df4 = pd.crosstab(df['koulutus'], df['sukup'], normalize = 'columns')
df4.index = koulutus
df4.columns = sukup
df4.style.format('{:.1%}')

Rivi-indeksin tekstimuotoiset arvot otan aiemmin määritellystä koulutus-listasta ja sarakkeen arvot sukup-listasta. Tulokset muotoilen prosenteiksi yhdellä desimaalilla.

olennaiset10

Tämän voin esittää pylväinä:

ax = df4.plot.barh()
plt.xlabel('Prosenttia sukupuolesta')
vals = ax.get_xticks()
ax.set_xticklabels(['{:.0%}'.format(x) for x in vals])

olennaiset11

Vaihtoehtoisesti voin käyttää 100% pinottuja vaakapylväitä. Tätä varten minun pitää ensin vaihtaa dataframen rivit ja sarakkeet päittäin transpose()-funktiolla:

ax = df4.transpose().plot.barh(stacked = True)
plt.xlabel('Prosenttia sukupuolesta')
vals = ax.get_xticks()
ax.set_xticklabels(['{:.0%}'.format(x) for x in vals])

olennaiset12

Useiden muuttujan yhteenvedot samaan taulukkoon: value_counts()

Esimerkkiaineistossani on muuttujina muiden muassa ’tyytyväisyys johtoon’, ’tyytyväisyys työtovereihin’, ’tyytyväisyys työympäristöön’, ’tyytyväisyys palkkaan’ ja ’tyytyväisyys työtehtäviin’. Näitä kaikkia on mitattu 5-portaisella tyytyväisyysasteikolla. Jotta voin vertailla tyytyväisyyksiä eri asioihin, haluan ne kaikki samaan taulukkoon vierekkäin. Tämän voin toteuttaa monellakin tavalla, mutta mielestäni kätevin tapa on käyttää value_counts()-funktiota:

df5 = df['johto'].value_counts(sort = False, normalize = True).to_frame()
df5['työtov'] = df['työtov'].value_counts(sort = False, normalize = True)
df5['työymp'] = df['työymp'].value_counts(sort = False, normalize = True)
df5['palkkat'] = df['palkkat'].value_counts(sort = False, normalize = True)
df5['työteht'] = df['työteht'].value_counts(sort = False, normalize = True)
df5.index = tyytyväisyys
df5.style.format('{:.1%}')

Ensimmäisellä rivillä lasken ensimmäiselle tyytyväisyys-muuttujalle vastausvaihtoehtojen 1-5 lukumäärät value_counts()-funktiolla. Lisäparametreina määritän

  • järjestämisen muuttujan arvojen mukaan (sort = False; ilman tätä järjestäminen tapahtuisi lukumäärien mukaiseen järjestykeen)
  • prosenttien esittämisen lukumäärien sijasta (normalize = True).

Lisäksi muunnan tuloksena syntyvät series-tyyppisen muuttujan dataframeksi (to_frame()).

Seuraavilla riveillä lisään dataframeen muiden tyytyväisyys-muuttujien lukumäärät.

Lopuksi lisään tekstimuotoiset rivi-indeksin arvot aiemmin määritellystä tyytyväisyys-listasta. Tulokset näytän prosenttimuodossa yhden desimaalin  tarkkuudella.

olennaiset13

Tämän voin esittää myös pinottuna pylväskuviona:

ax = df5.transpose().plot.barh(stacked = True, 
   color=['#C44E52','#D65F5F','grey','#4878CF','#4C72B0'])
vals = ax.get_xticks()
ax.set_xticklabels(['{:.0%}'.format(x) for x in vals])

Määritän itse värit oletusvärien sijaan.

olennaiset14

Luokiteltu jakauma: pd.cut()

Määrälliset muuttujat kannattaa yleensä luokitella ennen lukumäärien ja prosenttien laskemista. Esimerkkiaineistossani palkka on tällainen muuttuja.

Seuraavassa määritän luokkarajat (bins) ja luon uuden sarakkeen palkkaluokkaa varten. Varsinaisen luokittelun teen pd.cut()-funktiolla.

bins = [1000, 2000, 3000, 4000, 7000]
df['palkkaluokka'] = pd.cut(df['palkka'], bins = bins)
df6 = pd.crosstab(df['palkkaluokka'], 'n')
df6.columns.name = ''
df6

olennaiset15

Huomaa sulkumerkkien merkitys: kaarisulku tarkoittaa, että arvo ei kuulu luokkaan ja hakasulku tarkoittaa, että arvo kuuluu luokkaan. Esimerkiksi toisessa luokassa 2000 ei sisälly luokkaan, mutta 3000 sisältyy.

Palkkajakauman voin esittää histogrammina:

df['palkka'].plot.hist(bins)
plt.xlabel('Palkka')
plt.ylabel('Lukumäärä')

olennaiset16

Tunnuslukuja: describe()

Keskeisimmät tilastolliset tunnusluvut (lukumäärä, keskiarvo, keskihajonta, viiden luvun yhteenveto) lasken describe()-funktiolla. Seuraavassa lasken tunnuslukuja usealle muuttujalle ja muotoilen tunnusluvut näytettäväksi kahden desimaalin tarkkuudella:

df[['ikä', 'palveluv', 'palkka', 'johto', 'työtov', 
   'työymp', 'palkkat', 'työteht']].describe().
   style.format('{:.2f}')

olennaiset17

Pivot-taulukoita pivot_table(values = ”, index = ”, columns = ”)

Pivot-taulukoilla voin laatia monipuolisia raportteja eri tunnuslukuja käyttäen. Oletuksena pivot_table() -funktio laskee keskiarvoja. pivot_table()-funktion parametreinä ovat values (muuttuja, josta lasketaan), index (muuttujat, joiden arvot otetaan riveille) ja columns (muuttujat, joiden arvot otetaan sarakkeisiin). Esimerkiksi palkkakeskiarvot sukupuolen, perhesuhteen ja koulutuksen mukaan ryhmiteltyinä saan seuraavasti:

df7 = df.pivot_table(values = 'palkka', index = 
   ['sukup', 'perhe'], columns = 'koulutus')
df7.style.format('{:.0f}')

olennaiset18

Haluan tietysti täydentää taulukkoa sukupuolen, perhesuhteen ja koulutuksen tekstimuotoisilla arvoilla. Rivi-indeksin arvot ovat nyt kahdella tasolla ja tämän vuoksi tekstimuotoiset arvot määritellään set_levels-funktiolla:

df7.index = df7.index.set_levels(sukup, level=0)
df7.index = df7.index.set_levels(perhe, level=1)
df7.columns = koulutus
df7.style.format('{:.0f}')

olennaiset19

Voin laskea pivot-taulukkoon myös muita tunnuslukuja aggfunc-parametrin avulla. Seuraavassa lasken palkalle minimin, mediaanin, keskarvon ja maksimin eri koulutustasoille. Huomaa, että funktioina käytän numpy-kirjaston (np) funktioita.

df8 = df.pivot_table(values = 'palkka', index = 'koulutus',  
   aggfunc = [np.min, np.median, np.mean, np.max])
df8.index = koulutus
df8.columns = ['pienin', 'mediaani', 'keskiarvo', 'suurin']
df8.style.format('{:.0f}')

olennaiset20

Ruutu- ja janakaavio:  boxplot()

Ruutu- ja janakaavion (laatikko- ja viiksikaavio, boxplot, box & whisker) avulla voin visualisoida viiden luvun yhteenvedon. Seuraavassa esitän palkalle viiden luvun yhteenvedon eri koulutustasoilla:

ax = df.boxplot('palkka', by = 'koulutus')
plt.title('')
plt.suptitle('')
plt.xlabel('Koulutus')
plt.ylabel('Palkka')
ax.set_xticklabels(koulutus)

Tässä käytän pandas-kirjaston boxplot()-funktiota, joka tuottaa oletuksena kaksi otsikkoa (title, suptitle). Koodissa poistin otsikot näkyviltä. Vaaka-akselille lisäsin koulutukselle tekstimuotoiset arvot.

olennaiset21

Korrelaatio: corr()

Korrelaatiokertoimet lasken corr()-funktiolla. Seuraavassa lasken iän, palveluvuosien ja palkan väliset korrelaatiokertoimet ja esitän ne kahden desimaalin tarkkuudella:

df[['ikä', 'palveluv', 'palkka']].corr().style.format('{:.2f}')

olennaiset22

Hajontakaaviona voin esittää kahden muuttujan välisen korrelaation visuaalisesti:

df.plot.scatter('ikä', 'palkka')

olennaiset23

Tulosten siirtäminen raporttiin

Taulukot

Taulukoiden ulkoasu riippuu jonkin verran käyttämästäsi selaimesta (Jupyter notebook toimii oletusselaimessasi). Tämän artikkelin esimerkeissä olen käyttänyt Microsoftin Edge-selainta.

Julkaistavaksi tarkoitetun taulukon voin valita, kopioida ja liittää (copy – paste) esimerkiksi PowerPointiin, Wordiin tai Exceliin. Liitetty taulukko on myös taulukko ja voin muokata sitä taulukkotyökaluilla.

Python käyttää desimaalipilkkuna pistettä. PowerPointissa ja Wordissä voin helposti korvata pisteet pilkulla. Excelissä osa desimaalipisteellä varustetuista luvuista muuttuu päivämääriksi. Tämän voin estää kierrättämällä taulukko Wordin kautta ja korvaamalla pisteet pilkuilla Wordissä.

Voin myös tallentaa taulukot csv-tiedostoon. Tällöin desimaalipilkkujen kanssa ei tule ongelmia. Katso esimerkki artikkelista Frekvenssitaulukot Exceliin.

Jos taulukko kelpaa sellaisenaan julkaistavaksi, niin voin toki leikata sen ja liittää kuvana raporttiin.

Kuviot

Kuvion voin tallentaa plt.gcf().savefig -komennolla (gcf = get current figure). Tallennusformaatti määrittyy  kuviolle annettavan nimen tarkentimesta (esimerkiksi png):

plt.gcf().savefig('kuva.png')

Järjestelmäsi tukemat kuvaformaatit saat selville komennolla

plt.gcf().canvas.get_supported_filetypes()

Jos tarvitset tietyn kokoisia kuvia tai haluat käyttää tietynlaista tai tietynkokoista fonttia, niin voit tehdä määritykset heti ohjelmakirjastojen tuonnin jälkeen (ei kuitenkaan samassa Jupyter notebookin solussa kuin ohjelmakirjastojen tuonti, koska se ei jostain syystä toimi). Esimerkkejä mahdollisista määrityksistä:

plt.rc('figure', figsize = (8, 6))
plt.rc('font', family = 'sans-serif', size = 8)
plt.rc('axes', titlesize = 8 )
plt.rc('axes', labelsize = 8)
plt.rc('xtick', labelsize = 8)
plt.rc('ytick', labelsize = 8)
plt.rc('legend', fontsize = 8)

Kuvailevasta analyysistä merkitsevyystestaukseen

Jos kuvailet otosta ja haluat edetä testaamaan havaittujen erojen ja/tai riippuvuuksie tilastollista merkitsevyyttä, niin lue artikkelini

Merkitsevyyden testaus Pythonilla

Kuviot Pythonilla 3

Kategoristen muuttujien jakauman esitetän lukumäärä- tai prosenttipylväitä käyttäen. Näistä voit lukea lisää artikkeleistani Kuviot Pythonilla 1 ja Kuviot Pythonilla 2. Tässä artikkelissa esitän esimerkkejä määrällisen muuttujien kuvaamisesta. Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/kuviot3.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.

Ensimmäiseksi minun täytyy ottaa käyttöön tämän artikkelin kuvioissa tarvittavat kirjastot:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
plt.style.use('seaborn-white')

Tyylin ’seaborn-white’ sijasta voit käyttää sinulle mieluista tyyliä.

Avaan seaborn-kirjastoon sisältyvän esimerkkiaineiston tips (tietoja ravintola-asiakkaiden ravintolakäynneistä):

tips = sns.load_dataset('tips')
tips.head()

tips

Histogrammi

Ensimmäiseksi kuvaan ravintola-asiakkaiden laskujen loppusumman jakauman histogrammina:

ax=tips['total_bill'].plot.hist(bins=5)
ax.set_xlabel('total_bill')

hist1

Lisämääre bins=5 määrittää luokkien (histogrammin pylväiden) lukumäärän.

Voin myös itse määrittää luokkarajat:

ax=tips['total_bill'].plot.hist(bins=[0,10,20,30,40,50,60], 
   edgecolor='white')
ax.set_xlabel('total_bill')

hist2

Antamani luokkarajat tuottavat luokat [0,10), [10,20), [20,30), [30,40), [40,50) ja [50,60]. Luokan alaraja siis sisältyy aina luokkaan, mutta luokan yläraja ei sisälly luokkaan viimeistä luokkaa lukuun ottamatta.

Lisämääre edgecolor=’white’ lisää pylväille valkoisen reunan.

Seuraavaksi kuvaan palvelurahan määrän jakaumaa. Jos haluan esittää histogrammissa lukumäärien sijasta prosentit, niin voin käyttää esimerkiksi seuraavaa esoteerisen näköistä koodia:

weights=np.ones_like(tips['tip'])/float(len(tips['tip']))
ax=tips['tip'].plot.hist(bins=[0,2,4,6,8,10], weights=weights, 
   edgecolor='white')
ax.set_xlabel('tip')
ax.set_ylabel('%')
vals = ax.get_yticks()
ax.set_yticklabels(['{:.0f} %'.format(y*100) for y in vals])

hist3

weights vaatinee selitystä:

Otin edellä käyttöön numpy-kirjaston (import numpy as np). Numpy (numerical python) on peruskirjasto numeerisen tiedon käsittelyyn. Tässä käytän np.ones_like toimintoa, joka korvaa kunkin ravintola-asiakkaan palvelurahan ykkösellä. Jakamalla ykkösen palvelurahaa maksaneiden lukumäärällä (float(len(tips[’tip’]) saan selville prosenttiosuuden kaikista palvelurahaa maksaneista. Kaikkiaan weights=np.ones_like(tips[’tip’])/float(len(tips[’tip’])) muodostaa sarjan prosenttiosuuksia, joilla voin painottaa histogrammin lukumääriä ja näin lopulta saan histogrammiin prosentit.

Todennäköisyysjakauman estimointi

Voin edetä histogrammista taustalla olevan todennäköisyysjakauman tiheysfunktion estimointiin käyttämällä seaborn-kirjaston distplot-toimintoa:

sns.distplot(a=tips['tip'].dropna(), bins=[0,2,4,6,8,10])

distplot1

distplot antaa virheilmoituksen puuttuvista arvoista. Tämän vuoksi poistan dropna()-funktiolla mahdollisia puuttuvia arvoja sisältävät rivit. Kuvion pystyakselin arvot ovat todennäköisyysjakauman tiheysfunktion arvoja. Todennäköisyysjakauma estimoidaan ei-parametrisella KDE-menetelmällä (kernel density estimation). Voit lukea Wikipediasta lisää KDE-menetelmästä https://en.wikipedia.org/wiki/Kernel_density_estimation

Lisämääreellä kde=False voin laatia pelkän histogrammin:

sns.distplot(a=tips['tip'].dropna(), bins=[0,2,4,6,8,10], 
   kde=False, rug=True)

distplot2

Luokkien lukumäärien lisäksi voin tarkastella arvojen jakaumia luokkien sisällä alareunan viivoista. Nämä tulivat kuvioon lisämääreen rug=True ansiosta.

Ruutu- ja janakaavio

Ruutu- ja janakaavio on tehokas tapa määrällisen muuttujan jakauman tarkasteluun kategorisen muuttujan määräämissä luokissa. Voin esimerkiksi tarkstella palvelurahan suuruutta eri kokoisten seurueiden kohdalla:

ax=tips.boxplot(column='tip', by='size')
ax.set_title('')
plt.suptitle('')
ax.set_ylabel('tip')

box1

Ruutu- ja janakaavio vaatii lähtötietoinaan kuvattavan muuttujan (column) ja luokittelevan muuttujan (by). Jos ruutu- ja janakaavio ei ole sinulle entuudestaan tuttu, niin lue lisää artikkelistani Ruutu- ja janakaavio.

Voin laatia ruutu- ja janakaavion kätevämmin seaborn-kirjaston avulla. Esimerkiksi palvelurahat seurueen koon mukaan luokiteltuna sukupuolittain:

sns.boxplot(x='size', y='tip', hue='sex', data=tips)

box2

Pairplot

Kahden muuttujan välisiä riippuvuuksia voin tarkatella seaborn-kirjaston pairplot-kuviona.

Käytän esimerkkinä aineistoa, jossa on eri aiheiden osaamista/lahjakkuutta kuvaavia pistemääriä ja arvosanoja sekä opintomenestystä kuvaava pistemäärä.

opintomenestys = pd.read_excel('http://taanila.fi/
   opintomenestys.xlsx')
opintomenestys.head()

opintomenestys
Voin laatia kaikki parittaiset hajontakuviot useammasta muuttujasta:

sns.pairplot(opintomenestys[['verbaalinen','looginen','kielet',
   'matematiikka','opintomenestys']], kind='reg')

pairplot1

Yllä on näkyvillä vain osa kuvioista. Tuloksena saan jokaisesta muuttujasta histogrammin ja kaikki parittaiset hajontakaaviot pienimmän neliösumman regressiosuoralla täydennettynä. Voin tarkastella asiaa myös sukupuolittain:

sns.pairplot(opintomenestys[['verbaalinen','looginen','kielet',
   'matematiikka','opintomenestys','sukupuoli']], 
   hue='sukupuoli', kind='reg')

pairplot2

Histogrammien sijasta saan KDE-menetelmällä estimoidut todennäköisyysjakaumat miehille (oranssi) ja naisille (sininen). Jos haluan histogrammit, niin voin käyttää lisäparametria diag_kind=’hist’.

Jointplot

Yksittäisen muuttujaparin hajontakuvion saan kätevimmin seaborn-kirjaston jointplot-toiminnolla:

sns.jointplot(x='kielet', y='opintomenestys', data=opintomenestys, 
   kind='reg')

jointplot1

Kuvion reunoilla on muuttujien histogrammit sekä KDE-menetelmällä estimoidut todennäköisyysjakumat.

Tarvittaessa voin laskea kuvioon korrelaatiokertoimen ja sen merkitsevyyttä mittaavan p-arvon. Korrelaatiokertoimen laskemiseen tarvittavat funktiot löytyvät scipy.stats-kirjastosta. Pearsonin korrelaatiokerroin löytyy pearsonr-nimellä ja Spearmanin järjestyskorrelaatiokerroin spearmanr-nimellä.

from scipy.stats import spearmanr
sns.jointplot(x='matematiikka', y='opintomenestys', data=opintomenestys, 
   kind='reg', stat_func=spearmanr)

jointplot2

Aikasarjaennustaminen 3

Tämä artikkeli on jatkoa artikkeleille Aikasarjaennustaminen 1 ja Aikasarjaennustaminen 2.

Edellisen artikkelin Aikasarjaennustaminen 2 lopussa totesin, että esimerkkinä käyttämässäni aikasarjassa on neljän vuosineljänneksen välein toistuvaa kausivaihtelua, joka on syytä huomioida ennustamisessa. Tässä artikkelissa tarkastelen kausivaihtelun huomioivaa Holt-Winterin menetelmää.

Holt-Winterin tulomallissa aikasarjan tason L (level) hetkellä t määrittää lauseke

Lt = alfa * Yt/St-s + (1 – alfa)(Lt-1 + Tt-1)

Yllä Yt on viimeisin havainto, St-s on edellisen vastaavan periodin kausivaihtelu ja  Tt-1 on edellinen trendi.

Trendille T hetkellä t saadaan arvio lausekkeesta

Tt = beta * (Lt – Lt-1) + (1 – beta) * Tt-1

Kausivaihtelulle S hetkellä t saadaan arvio lausekkeesta

St = gamma * Yt/Lt + (1 – gamma) * St-s

Ennuste hetkelle t + p saadaan

(Lt + pTt)St-s

Yllä on kyse Holt-Winterin tulomallista, jossa kausivaihtelu huomioidaan kausivaihtelukertoimena. Holt-Winterin mallia voidaan soveltaa myös summamallina, jolloin kausivaihtelu huomioidaan lisättävänä kausivaihteluterminä. Tulomalli soveltuu paremmin tilanteisiin, joissa kausivaihtelukomponentin suuruus vaihtelee aikasarjan tason L mukaan. Summamalli soveltuu tilanteisiin, joissa kausivaihtelukomponentin suuruus ei riipu aikasarjan tasosta L.

Mallin parametrit alfa, beta ja gamma pyritään määrittämään siten että virheiden keskihajonta RMSE saa pienimmän mahdollisen arvonsa.

Python toteutus

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

https://github.com/taanila/tilastoapu/blob/master/forecast3.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.

Kirjastot

Tarvitsen monia ohjelmakirjastoja: pandas dataframen käsittelyyn, matplotlib kuvioiden tekemiseen, statsmodels eksponentiaaliseen tasoitukseen, sklearn ennustevirhettä kuvaavien tunnuslukujen laskemiseen ja math neliöjuuren laskemiseen.

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from statsmodels.tsa.api import Holt
from sklearn.metrics import mean_squared_error, mean_absolute_error
from math import sqrt

Aikasarja

Tallennan vuosineljänneksittäisiä myyntilukuja sisältävän aikasarjan data-nimiseen dataframeen:

series=[500,350,250,400,450,350,200,300,350,200,150,
   400,550,350,250,550,550, 400,350,600,750,500,400,650,850]
index=pd.date_range('2000-03-31', periods=25, freq='Q')
data=pd.DataFrame(series, index=index)
data.columns=['Demand']

Ennustemalli

Tämän jälkeen sovitan eksponentiaalisen mallin aikasarjaan ja tallennan mallin mukaiset sovitetut arvot datan Predict-sarakkeeseen. Jos haluan käyttää summamallia, niin korvaan parametrin seasonal=’mul’ parametrilla seasonal=’add’.

fit1 = ExponentialSmoothing(data['Demand'], seasonal_periods=4, 
   trend='add', seasonal='mul').fit()
data['Predict']=fit1.fittedvalues 
data.plot()

holtwinter1

Silmämäärin katsottuna Holt-Winterin malli istuu hyvin aikasarjaan. Tarkistan asian virheiden suuruutta kuvaavista tunnusluvuista. Ensiksi RMSE:

sqrt(mean_squared_error(data['Demand'], data['Predict']))

Tulokseksi saan RMSE = 53 ( yksinkertaisessa eksponentiaalisessa tasoituksessa 150)

MAD:

mean_absolute_error(data['Demand'], data['Predict'])

Tulokseksi saan MAD = 42 (yksinkertaisessa eksponentiaalisessa tasoituksessa 128)

Virheitä kannattaa tarkastella myös sellaisenaan:

data['Resid']=fit1.resid
data['Resid'].plot()

holtwinter2

Virheiden vaihtelu näyttää satunnaiselta niin kuin hyvässä mallissa pitää ollakin.

Ennuste

Lasken vielä ennusteet neljälle seuraavalle neljännekselle omaan dataframeen ja piirrän samaan kuvioon alkuperäisen aikasarjan ja neljän  neljänneksen ennusteet.

index=pd.date_range('2006-06-30', periods=4, freq='Q')
datap=pd.DataFrame(fit1.forecast(4), index=index)
datap.columns=['Predict']
data['Demand'].plot()
datap['Predict'].plot()

holtwinter3

Ennusteestakin (oranssi) näkee, että kausivaihtelu on mallissa huomioitu.

Mallin parametreista selviävät alfan (smoothing_level), betan (smoothing_slope), ja gamman (smoothing_seasonal) arvot sekä tason, trendin ja kausivaihtelun alkuarvot :

fit1.params

{’damping_slope’: nan,
’initial_level’: 571.4286398129714,
’initial_seasons’: array([0.84785489, 0.56435295, 0.41577146, 0.71556852]),
’initial_slope’: 18.700003064693195,
’lamda’: None,
’remove_bias’: False,
’smoothing_level’: 0.8700136796734163,
’smoothing_seasonal’: 0.0,
’smoothing_slope’: 1.818780385451967e-19,
’use_boxcox’: False}

Datan pika-analyysi Pythonilla

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

https://github.com/taanila/tilastoapu/blob/master/kuvailu.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.

Muuttujatyypit

Voin jakaa analysoitavat muuttujat esimerkiksi seuraavasti:

  • Kategoriset: Näille voin laskea lukumäärä- ja prosenttiyhteenvetoja (frekvenssitaulukot ja ristiintaulukoinnit).
  • Mielipideasteikolliset: Näille voin laskea samat kuin kategorisillekin. Tietyin varauksin voin laskea myös tilastollisia tunnuslukuja kuten keskiarvon ja keskihajonnan.
  • Määrälliset eli kvantitatiiviset: Näille voin laskea tilastollisia tunnuslukuja ja muuttujien välistä riippuvuutta kuvaavat korrelaatiokertoimet.

Pythonilla on suhteellisen helppoa kirjoittaa koodi, joka laskee kategorisille ja mielipideasteikoille frekvenssitaulukot ja kaikki mahdolliset ristiintaulukoinnit sekä määrällisille tunnusluvut ja korrelaatiot, myös kategroristen muuttujien määräämissä ryhmissä.

Frekvenssitaulukot

Laskentaa varten kategoriset, mielipideasteikolliset ja määrälliset muuttujat on määriteltävä. Koodissa tämän voi tehdä seuraavaan tapaan:

kategoriset=['sukup','perhe','koulutus']
mielipiteet=['johto', 'työtov', 'työymp', 'palkkat', 'työteht']
kvantit=['ikä','palveluv','palkka']

Laskettavat tulokset on hyvä tallentaa jonnekin. Voin tallentaa tulokset .csv-muotoiseen tiedostoon, jonka voin myöhemmin avata Exceliin. Uuden tiedoston voin luoda komennolla

f=open('example1.csv', 'a')

Jos en laita tiedoston nimen eteen polkua, niin tiedosto tallentuu siihen kansioon, johon koodikin on tallennettu. Muista huolehtia ettei kansiossa ole entuudestaan saman nimistä tiedostoa! Lisäargumentti ’a’ tarkoittaa tiedoston avaamista uuden tiedon lisäämistä varten.

Kategoristen muuttujien frekvenssitaulukot voin laskea ja kirjoittaa tiedostoon seuraavasti (df viittaa dataframeen, jossa on analysoitava aineisto):

if kategoriset:
   for var in kategoriset:
      df1=pd.crosstab(df[var],'lkm')
      df1['prosenttia'] = df1/df1.sum()
      df1.loc['Yhteensä'] = df1.sum()
      df1.to_csv(f, sep=';', decimal=',')
      f.write('\n')

if kategoriset: testaa onko kategorisia muuttujia mukana. Jos kategoristen muuttujien lista on tyhjä, niin frekvenssitaulukoita ei lasketa.

for var in kategoriset: Aloittaa silmukan, joka toistaa seuraavat koodirivit kaikille kategorisille muuttujille.

df1=pd.crosstab(df[var],’lkm’) laskee frekvenssitaulukon.

df1[’prosenttia’] = df1/df1.sum() luo frekvenssitaulukkoon uuden sarakkeen ja laskee siihen prosentit.

df1.loc[’Yhteensä’] = df1.sum() lisää frekvenssitaulukon alareunaan summarivin.

df1.to_csv(f, sep=’;’, decimal=’,’) tallentaa frekvenssitaulun tiedostoon. Suomessa kannattaa käyttää erottimena puolipistettä ja desimaalierottimena pilkkua, jotta tiedot aukenevat ilman desimaalipilkkuun liittyviä ongelmia.

f.write(’\n’) kirjoittaa tiedostoon tyhjän rivin.

Muista, että Pythonissa osoitetaan sisennyksellä if-rakenteeseen liittyvä koodi samoin kuin for-rakenteeseen liittyvä koodi. Mitään erillistä päättymiskomentoa ei if-  tai for-rakenteelle tarvita, vaan rakenteen päättyminen osoitetaan sisennyksen päättymisellä.

Mielipideasteikollisille muuttujille voin laskea frekvenssitaulukot samanlaisella koodilla kuin kategorisillekin.

Ristiintaulukoinnit

Kaikki mahdolliset kategoristen muuttujien väliset ristiintaulukoinnit voin laskea seuraavasti:

if kategoriset:
   for var1 in kategoriset:
      for var2 in kategoriset:
         if var1!=var2:
            df1=pd.crosstab(df[var2],df[var1])
            df1.loc['Yhteensä']=df1.sum()
            df2=pd.crosstab(df[var2],df[var1],normalize='columns')
            df2.index.name=var2+'/'+var1
            df2.loc['Yhteensä']=df2.sum()
            df2.loc['n']=df1.loc['Yhteensä']
            df2.to_csv(f, sep=';', decimal=',')
            f.write('\n')

Kaikkien mahdollisten muuttujaparien läpikäymiseen tarvitsen kaksi sisäkkäistä for-silmukkaa. Lisäksi varmistan if-rakenteella, että muuttujaa ei ristiintaulukoida itsensä kanssa.

df1=pd.crosstab(df[var2],df[var1]) laskee ristiintaulukoinnin lukumäärinä.

df1.loc[’Yhteensä’]=df1.sum() lisää ristiintaulukoinnin alareunaan summarivin.

df2=pd.crosstab(df[var2],df[var1],normalize=’columns’) laskee ristiintaulukoinnin, jossa esitetään sarakeprosentit.

df2.index.name=var2+’/’+var1 lisää muuttujien nimet toisen ristiintaulukoinnin vasempaan yläkulmaan.

df2.loc[’Yhteensä’]=df1.sum() lisää toisen ristiintaulukoinnin alareunaan summarivin.

df2.loc[’n’]=df1.loc[’Yhteensä’] lisää ristiintaulukoinnin alareunaan ylimääräisen rivin n-arvoja varten (mistä lukumääristä prosentit on laskettu). n-arvot haetaan ensimmäisen ristiintaulukoinnin summa-riviltä.

Lopuksi toinen ristiintaulukointi kirjoitetaan tiedostoon samalla tavalla kuin frekvenssitaulukotkin.

Kategoristen ja mielipideasteikollisten väliset ristiintaulukoinnit voin laskea samanlaisella koodilla kuin kategoristen väliset ristiintaulukoinnit.

Tunnusluvut

Tunnusluvut lasken describe()-funktiolla. Tiedostoon kirjoittamisen yhteydessä käännän tunnuslukutaulukon (vaihdan rivit ja sarakkeet) transpose()-funktiolla, jotta saan mielestäni helppolukuisemman taulukon.

if mielipiteet:
   df1=df[mielipiteet].describe()
   df1.transpose().to_csv(f, sep=';', decimal=',')
   f.write('\n')

Tunnusluvut kategoristen muuttujien määräämissä ryhmissä lasken kahta sisäkkäistä for-silmukkaa hyödyntäen. Taustamuuttujan mukaisiin ryhmiin jakamisen hoidan groupby()-funktiolla.

if kategoriset and mielipiteet:
   for var1 in kategoriset:
      for var2 in mielipiteet:
         df1=df.groupby(var1)[var2].describe()
         df1.index.name=var2+'/'+var1
         df1.to_csv(f, sep=';', decimal=',')
         f.write('\n')

Korrelaatiot

Määrällisten muuttujien korrelaatiokertoimet saan corr()-funktiolla:

if kvantit:
   df1=df[kvantit].corr()
   df1.index.name='Korrelaatiot'
   df1.to_csv(f, sep=';', decimal=',')
   f.write('\n')

Tiedoston sulkeminen

Koodin lopuksi on tärkeää sulkea tiedosto f.close() -komennolla!

Koodin käyttö

Koodin käyttämiseksi täytyy tietenkin tuoda tarvittavat kirjastot ja avata analysoitava aineisto dataframeen:

import pandas as pd

df=pd.read_excel('http://taanila.fi/data2.xlsx', sheet_name='Data')
df.head()

df.head()-komennolla voit tutustua aineistoon ja selvittää eri muuttujien tyypit (kategorinen, mielipideasteikollinen, määrällinen).

Voit muokata ja täydentää koodia omiin tarpeisiisi sopivaksi.

 

Kuviot Pythonilla 2

Tässä artikkelissa perehdyn seaborn-kirjaston pylväskuvioihin. Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/kuviot2.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.

Matplolib ja Seaborn

Kuvioiden luomisen kulmakivi on matplotlib-kirjasto. Lue lisää artikkelista Kuviot Pythonilla 1. Seaborn on matplotlib-kirjaston päälle rakennettu kirjasto, joka tarjoaa helpon käyttöliittymän monien muuten vaikeasti laadittavien kaavioiden laatimiseen.

Seabornia käyttäessäni aloitan tuomalla tarvittavat kirjastot:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('ggplot')

Tyylinä voit toki käyttää ggplot sijasta sinulle mieleistä tyyliä.

Harjoittelun helpoittamiseksi seaborn-kirjasto sisältää joitain valmiita esimerkki datoja. Käytän aluksi dataa Titanic-laivan matkustajista, jonka voin avata komennolla:

titanic = sns.load_dataset('titanic')
titanic.head()

Countplot

Seabornin countplot tarjoaa helpon käyttöliittymän kategorisen muuttujan jakauman esittämiseen. Kuvaan esimerkkinä hyttiluokan (first class , second class, third class) jakauman:

ax=sns.countplot(x='class', data=titanic, color='C0')
ax.set_ylabel('lukumäärä')

sns1

Huomaa erityisesti seuraavat seikat:

  • countplot tekee pylväskuvion frekvensseistä suoraan datasta ilman että minun täytyy ensin laskea frekvenssit
  • countplot-komennon käyttöliittymä on yksinkertainen: x määrittää x-akselin muuttujan ja data määrittää käytettävän datan
  • jos tallennat axes-objektin muuttujan arvoksi, niin pääset käyttämään kuvion muotoilussa  matplotlibin toimintoja.

Seuraavaksi esitän hyttiluokkien jakauman erikseen miehille, naisille ja lapsille. Tähän käytän hue-määrettä:

sns.countplot(x='class', hue='who', data=titanic)

sns2

Vaakapylväikön saan seuraavasti:

sns.countplot(y='class', hue='who', data=titanic)

sns3

Factorplot

Seuraavaksi esitän hyttiluokkien jakauman omana kuvionaan haaksirikossa menehtyneille ja siitä selvinneille:

sns.factorplot(x='class', hue='who', data=titanic, col='survived', 
   kind='count')

sns4

col-määreellä määrään minkä muuttujan perusteella jako eri kuvioihin tehdään. factorplot osaa tehdä useita kaaviolajeja; tässä määrään tehtäväksi lukumääriä esittävän pylväskuvion (kind=’count’).

Barplot

barplot tekee pylväskaavion, joka esittää oletuksena keskiarvot ja keskiarvojen virhemarginaalit:

sns.barplot(x='class', y='survived', hue='sex', data=titanic)

sns5

Tässä käytin y-muuttujana survived-muuttujaa (0=menehtyi, 1=selvisi).

Katsotaan esimerkkejä myös toista aineistoa (tips) käyttäen. tips sisältää tietoja ravintola-asiakkaiden ravintolakäynneistä.

tips = sns.load_dataset("tips")
tips.head()

Kuvaan kuinka paljon naiset ja miehet ovat keskimäärin antaneet palvelurahaa eri päivinä:

sns.barplot(x='day', y='tip', hue='sex', data=tips)

sns6

Seuraavaksi kuvaan kuinka suuri tipin osuus keskimäärin oli laskun loppusummasta:

tips['tip%'] = tips['tip']/(tips['total_bill']-tips['tip'])
sns.barplot(x='day', y='tip%', data=tips)

sns7

Katsotaan lopuksi kolmannen esimerkkiaineiston avulla työntekijöiden tyytyväisyyttä eri asioihin. Avaan ensin aineiston ja erotan dataframeen df1 pelkästään tyytyväisyysmuuttujat:

df = pd.read_excel('http://taanila.fi/data1.xlsx', 
   sheet_name = 'Data')
df1=df[['johto', 'työtov', 'työymp', 'palkkat', 'työteht']]
df1.head()

Laadin kuvion määrittämättä erikseen x– tai y– tai hue-muuttujia. Tällöin seaborn kuvaa jokaisen datan muuttujan omana pylväinään (niin kutsuttu wide form dataframe).  Lisämääre orient=’h’ määrittää käytettäväksi vaakapylväitä (horizontal).

ax=sns.barplot(data=df1, orient='h', color='C0')
ax.set_xlim(1, 5)
ax.set_title('Tyytyväisyyskeskiarvoja')
ax.set_xlabel('1=erittäin tyytymätön - 5=erittäin tyytyväinen')

sns8

Halutessani voin kuvion nähtyäni järjestää pylväät pituusjärjestykseen:

ax=sns.barplot(data=df1, orient='h', color='C0', order=
   ['työtov','työteht','työymp','johto','palkkat'])
ax.set_xlim(1, 5)
ax.set_title('Tyytyväisyyskeskiarvoja')
ax.set_xlabel('1=erittäin tyytymätön - 5=erittäin tyytyväinen')