#!/usr/bin/env python # coding: utf-8 # In[1]: from datetime import datetime print(f'Päivitetty {datetime.now().date()} / Aki Taanila') # # Tutustu Pythoniin ja pandas-kirjastoon # # Tästä muistiosta voit aloittaa vaikka et olisi koskaan koodannut Pythonilla. # # Paketti (kutsutaan myös kirjastoksi) on kokoelma valmista ohjelmakoodia, jota voin käyttää omissa ohjelmissani. Data-analytiikan peruspaketti on nimeltään **pandas**. Pandas sisältää muiden muassa valmiin **crosstab**-funktion, jolla voin laskea lukumäärä- ja prosenttiyhteenvetoja sekä **describe**-funktion, jolla voin nopeasti laskea tilastolliset tunnusluvut. # # Useimmat data-analytiikassa tarvittavat paketit sisältyvät Colabiin ja Anacondaan. Jos käytät Minicondaa, niin sinun pitää asentaa jokainen käytettävä paketti erikseen. # # Jos paketti on valmiiksi asennettu, niin voin tuoda sen käyttööni **import**-komennolla. Esimerkiksi `import pandas as pd` tuo pandas-paketin käyttööni siten että voin seuraavissa koodisoluissa viitata siihen lyhenteellä **pd**. # # Muistion solun voit suorittaa näppäinyhdistelmillä **ctrl-enter** (pysyt samassa solussa) tai **shift-enter** (siirryt seuraavaan soluun). # ## Datan lukeminen # # Pandas-paketin **read_excel**-funktiolla voin lukea datan Excel-tiedostosta. Jos tiedosto on samassa kansiossa kuin muistio, niin voin viitata siihen pelkällä tiedostonimellä, esimerkiksi **'data1.xlsx'**. Seuraavassa tiedosto on kuitenkin nettiosoitteessa. Luen seuraavassa data-objektin **df**-nimisen (voit käyttää haluamaasi nimeä) muuttujan arvoksi. # # Merkkijonot kirjoitetaan heittomerkkien tai lainausmerkkien väliin. Heittomerkin saat enter-näppäimen vierestä (samassa näppäimessä kuin tähti eli asteriski). # In[2]: import pandas as pd df = pd.read_excel('https://taanila.fi/data1.xlsx') # Pandas-paketissa on määritelty **dataframe**-niminen tietorakenne, jota voit hyvin pitää Excel-taulukon vastineena. Edellä avattu data-olio **df** on dataframe. # # ## Dataan tutustuminen # # Käsky `df` näyttää datan ensimmäisimmät ja viimeisimmät rivit. # In[3]: df # Dataframe-oliolla on lukuisia ominaisuuksia ja funktioita, joista jatkossa esimerkkejä. Huomaa, että # # * ominaisuuden nimen perässä ei ole sulkumerkkejä # * funktion nimen perässä on aina sulkumerkit. # ## Kategorinen vai määrällinen muuttuja # # * **nro** Tämä muuttuja on keinotekoinen järjestysnumero eikä muuttujan tyypillä ole merkitystä. # * **sukup** Sukupuoli on kategorinen muuttuja. Tässä arvo 1 tarkoittaa miestä ja 2 naista. # * **ika** Ikä on määrällinen muuttuja. # * **perhe** Perhesuhde on kategorinen muuttuja. Tässä arvo 1 tarkoittaa perheetöntä ja 2 perheellistä. # * **koulutus** Koulutus on kategorinen muuttuja (1 = peruskoulu, 2 = 2. aste, 3 = korkeakoulu, 4 = ylempi korkeakoulu). # * **palveluv** Palvelusvuodet on määrällinen muuttuja. # * **palkka** Palkka on määrällinen muuttuja. # * **johto, työtov, työymp, palkkat, työteht** Nämä ovat tyytyväisyysmuuttujia, joiden arvo on välillä 1 = erittäin tyytymätön - 5 = erittäin tyytyväinen. Lähtökohtaisesti nämä ovat kategorisia muuttujia, mutta voidaan tulkita myös määrällisiksi muuttujiksi. Mielipideasteikot ovat usein rajatapauksia, joille voidaan käyttää sekä kategoristen että määrällisten muuttujien menetelmiä. # * **työterv, lomaosa, kuntosa, hieroja** Nämä ovat kategorisia muuttujia, jotka kertovat onko vastaaja käyttänyt (**1**) kyseistä etua. # In[4]: # shape-ominaisuus sisältää rivien ja sarakkeiden lukumäärät df.shape # In[5]: # info-funktio tulostaa tietoja datan sarakkeista df.info() # Yllä näkyvästä tulosteesta selviää, että tässä dataframessa on **int64** eli kokonaislukutyyppistä tietoa ja **float64** eli liukulukutyyppistä tietoa. Liukulukuja voit ajatella desimaalilukuina. # # Jos sarakkeessa olisi tekstimuotoista tietoa, niin se näyttäytyisi **object**-tyyppinä. # In[6]: # unique-funktio tulostaa sarakkeen ainutkertaiset arvot (järjestys tulee datasta) df['ikä'].unique() # ## Suodatuksia # # Datalle on helppo tehdä erilaisia suodatuksia. Dataframen sarakkeeseen voin viitata kirjoittamalla sarakkeen nimen hakasulkujen sisään, esimerkiksi `df['palkka']`. Suodatuksissa voin käyttää # # * vertailuoperaattoreita **>, <, >=, <=, ==, !=** # * yhdistämisoperaattoreita **&** (ja), **|** (tai) # * kielto-operaattoria **~** # In[7]: # 30-vuotiaat df[df['ikä']==30] # In[8]: # Ne joiden koulutus ei ole 1 ja palkka on korkeintaan 1700 (huomaa sulkumerkit) df[(df['koulutus']!=1) & (df['palkka']<=1700)] # In[9]: # Yli 50-vuotiaat alle 2000 palkalla (huomaa sulkumerkit) df[(df['ikä']>50) & (df['palkka']<2000)] # In[10]: # Ne, jotka eivät ole käyttäneet työterveyshuoltoa ja joiden palkka alle 2000 df[~(df['työterv']==1) & (df['palkka']<2000)] # ## Funktion parametrit ja argumentit # # Funktioille voidaan antaa lisätietoa parametreinä. Esimerkiksi **nsmallest**-funktiolle mahdolliset parametrit löytyvät funktion ohjesivulta https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.nsmallest.html. Jos annat parametrien arvot funktion ohjesivun mukaisessa järjestyksessä, niin parametreja ei tarvitse nimetä. Parametrien nimeäminen on kuitenkin hyvä tapa. Esimerkiksi seuraavassa solussa voisin käyttää nimettyjä parametreja: `df.nsmallest(n=3, columns='palkka')` # # Terminologiaa: **n** ja **columns** ovat nsmallest-funktion parametrejä, joille voidaan antaa arvo. Parametrille annettavaa arvoa kutsutaan argumentiksi. Seuraavassa **3** on parametrin **n** arvo eli argumentti. # In[11]: # Rivit, joilla kolmen pienintä palkkaa df.nsmallest(3, 'palkka') # In[12]: # Rivit, joilla viisi korkeinta ikää df.nlargest(5, 'ikä') # ## Tilastolliset tunnusluvut # # **Describe**-funktio laskee tilastolliset tunnusluvut: # # * arvojen lukumäärä (count) # * keskiarvo (mean) # * keskihajonta (std) # * pienin (min) # * alaneljännes (25%); neljäsosa arvoista on korkeintaan alaneljänneksen suuruisia # * mediaani (50%) # * yläneljännes (75%); kolme neljäsosaa arvoista on korkeintaan yläneljänneksen suuruisia # * suurin (max). # In[13]: df.describe() # In[14]: # palkan tunnusluvut df['palkka'].describe() # ## Frekvenssitaulukko # # Seuraavassa sijoitan **crosstab**-funktion antaman tuloksen **df1**-nimisen muuttujan arvoksi. Tällöin tulosta ei näytetä ellen anna erikseen käskyä `df1`. # # Lisätietoa **crosstab**-funktion parametreista https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.crosstab.html # In[15]: df1 = pd.crosstab(df['koulutus'], 'f') df1 # Crosstab laskee frekvenssit eli esiintymiskertojen lukumäärät. Esimerkiksi koulutuksen 1 on suorittanut 27 henkilöä. # # Crosstab-funktion tuottama taulukko on dataframe kuten alkuperäinen datakin. Dataframe koostuu aina kolmesta osasta: # # * indeksi (**index**), joka näkyy vasemmassa reunassa # * sarakeotsikot (**columns**), jotka näkyvät yläreunassa # * varsinainen data; yhdessä sarakkeessa voi olla vain yhdentyyppistä dataa (esimerkiksi kokonaislukuja). # # Yllä indeksinä ovat koulutus-sarakkeessa esiintyneet arvot 1, 2, 3 ja 4. Sarakeotsikoina on ainoastaan f (minulla on tapana käyttää f:ää frekvenssin tunnuksena). Indeksillä on nimi (koulutus) ja sarakeotsikoilla on nimi (col_0). # In[16]: # Vaihdan sarakeotsikoiden nimeksi tyhjän merkkijonon (peräkkäiset heittomerkit) df1.columns.name = '' df1 # **Lista** on Pythonin tietorakenne, joka kirjoitetaan hakasulkujen sisään. Seuraavassa määrittelen listan ja sijoitan sen dataframen indeksiin (**index**). # In[17]: koulutus = ['Peruskoulu', '2. aste', 'Korkeakoulu', 'Ylempi korkeakoulu'] df1.index = koulutus df1 # Jos viittaan sarakkeeseen, jota ei ole vielä olemassa, niin sellainen luodaan. Seuraavassa luon sarakkeen, johon lasken prosentit. # In[18]: # Havaintojen lukumäärä n = df1['f'].sum() # Lisään %-sarakkeen df1['%'] = df1['f']/n*100 df1 # Uuden rivin luomiseksi viittaan uuteen riviin **loc**-ominaisuudella. Seuraavassa lasken **sum**-funktiolla Yhteensä-rivin. # In[19]: df1.loc['Yhteensä'] = df1.sum() df1 # **VAROITUS!** Kokeile mitä tapahtuu Yhteensä-rivillä, jos suoritat yllä olevan solun uudelleen? Tämän jälkeen kannattaa suorittaa kaikki muistion solut uudelleen **Cell**-valikon komennolla **Run All**. # # Yllä mainitun varoituksen mukaisen vaaran voit välttää käyttämällä `df1.sum()` sijasta `df1.loc['Peruskoulu':'Ylempi korkeakoulu'].sum()` # # ## Ristiintaulukointi # # Crosstab-funktiolla voin laskea myös ristiintaulukoinnin. Seuraavassa lasken koulutuksen ja sukupuolen välisen ristiintaulukoinnin. # In[20]: df2 = pd.crosstab(df['koulutus'], df['sukup']) df2 # Voin sijoittaa koulutuksen tekstimuotoiset arvot (aiemmin määrittelemäni lista) indeksiin. Luon myös sukupuoli-listan ja sijoitan sen arvot sarakeotsikoihin. # In[21]: sukupuoli = ['Mies', 'Nainen'] df2.index = koulutus df2.columns = sukupuoli df2 # Dataframen indeksille voin antaa nimen. Samoin sarakeotsikoille voin antaa nimen. # In[22]: df2.index.name = 'Koulutus' df2.columns.name = 'Sukupuoli' df2 # ## Dataframen käsittelyä # Listan ohella **sanakirja** (**dictionary**) on toinen keskeinen pythonin tietorakenne. Sanakirja kirjoitetaan aaltosulkujen sisään ja se koostuu pareista. Parin jäsenet erotetaan toisistaan kaksoispisteellä. # # Seuraavassa käytän sanakirjaa **rename**-funktion yhteydessä kahden muuttujan (sarakkeen) uudelleen nimeämiseen. # In[23]: df = df.rename(columns={'sukup':'sukupuoli', 'palveluv':'palveluvuodet'}) # Tarkistan onnistuiko uudelleen nimeäminen df.columns # **VAROITUS!** Jos muuttujien uudelleen nimeämisen jälkeen palaat sellaiseen aiempaan soluun, jossa viitataan sukup- tai palveluv-muuttujaan, niin solun suorittaminen tuottaa virheilmoituksen. Tällaisessa tilanteessa kannattaa suorittaa kaikkien solujen koodit uudelleen **Cell**-valikon komennolla **Run All** # In[24]: # Indeksin järjestyksen vaihtaminen reindex-funktiolla df2.reindex(['Peruskoulu', 'Ylempi korkeakoulu', 'Korkeakoulu', '2. aste']) # Huomaa, että uusi järjestys ei tallennu dataframeen ellen varta vasten sijoita sitä df2:n arvoksi! # In[25]: # Sarakkeiden järjestyksen vaihtamiseksi annan axis-parametrille arvon 1 df2.reindex(['Nainen', 'Mies'], axis=1) # In[26]: # Indeksin siirtäminen pois indeksistä; tilalle oletusindeksi 0, 1,... df2 = df2.reset_index() df2 # In[27]: # Koulutuksen palauttaminen indeksiin df2 = df2.set_index('Koulutus') df2 # **Loc**-ominaisuudella voin viitata datan "viipaleisiin" indeksin arvoja ja sarakeotsikoita käyttäen. # In[28]: # Kuinka monta 2. asteen koulutuksen suorittanutta miestä? df2.loc['2. aste', 'Mies'] # Pelkkä kaksoispiste tarkoittaa kaikkia rivejä tai kaikkia sarakkeita. # In[29]: # df2:n 'Nainen'-sarakkeen kaikki rivit df2.loc[:, 'Nainen'] # Edellä dataa on vain yhdessä sarakkeessa ja sen vuoksi tulos ei ole tyypiltään dataframe, vaan **series**. Voin kuitenkin vaihtaa tuloksen dataframeksi. # In[30]: df2.loc[:, 'Nainen'].to_frame() # In[31]: # Kaikki sarakkeet, mutta koulutuksesta jätetään Ylempi korkeakoulu pois df2.loc['Peruskoulu':'Korkeakoulu', :] # ## Luokiteltu jakauma # # Luokkarajojen määrittämistä varten tarkistan pienimmän ja suurimman iän. # In[32]: df['ikä'].describe() # Voin muodostaa luokkia pandas-kirjaston **cut**-funktiolla. Ensin määritän luokkarajat listana ja annan listan **bins**-parametrin arvoksi. # In[33]: bins = [19, 29, 39, 49, 59, 69] df['ikäluokka'] = pd.cut(df['ikä'], bins=bins) df # Ikäluokat löytyvät viimeisestä sarakkeesta # In[34]: # Luokiteltu jakauma: df3 = pd.crosstab(df['ikäluokka'], 'f') df3.columns.name = '' df3 # ## Ryhmittely # # Seuraavassa lasken palkalle tilastollisia tunnuslukuja koulutuksen määräämissä ryhmissä. Datan jakaminen ryhmiin sujuu **groupby**-funktiolla. # # Seuraavaa komentoon on hyvä perehtyä tarkemminkin. Koodissa operoidaan enimmäkseen **olio**illa (voidaan kutsua myös objekteiksi). # # Esimerkiksi **df** on **DataFrame**-luokan **olio**. DataFrame-luokan oliot tunnistavat **groupby**-funktion. Groupby-funktiolle pitää antaa parametrina sen muuttujan nimi, jonka mukaan ryhmitellään. Huomaa, että olion nimen ja funktion nimen välissä on aina **piste** (**.**). Samoin käytetään pistettä olion nimen ja ominaisuuden välissä (esimerkiksi **df.shape**). # # Groupby-funktion käytön seurauksena syntyy uusi olio, jonka 'palkka'-muuttujaan koodi viittaa. Tälle oliolle toteutetaan **describe**-funktio, joka laskee tilastolliset tunnusluvut. # # Komennot siis usein muodostuvat oliosta, funktioista (funktion kohdistaminen olioon muodostaa uuden olion) ja ominaisuuksista, jotka aina erotetaan toisistaan pisteellä. # In[35]: df4 = df.groupby('koulutus')['palkka'].describe() # Aiemmin määritelty koulutus-lista sisältää koulutusten tekstimuotoiset arvot df4.index = koulutus df4 # In[36]: # Voin halutessani transponoida dataframen df4.T # ## Luettelo muistiossa käytetyistä funktioista ja ominaisuuksista # # # * Merkkijono (**string**) kirjoitetaan heittomerkkien '' väliin (myös lainausmerkit "" käyvät). Merkkijonoja ovat esimerkiksi tiedostonimet, dataframen sarakeotsikot (muuttujien nimet), muuttujien tekstimuotoiset arvot. Huomaa kuitenkin, että Pythonin muuttujien nimiä ei kirjoitetan heittomerkkien väliin (esimerkiksi df, n jne.) # # * Paketin tuonti **import**-toiminnolla; esimerkkinä **pandas**-paketin tuonti # # * Pythonin tietorakenne **lista**, joka kirjoitetaan hakasulkujen sisään # * Pythonin tietorakenne **sanakirja** (**dictionary**), joka kirjoitetaan aaltosulkujen sisään # * Pandas-kirjaston tietorakenteet **dataframe** ja **series** # # * **pd.read_excel()** datan lukeminen Excel-tiedostosta # * **df.shape** datan rivien ja sarakkeiden lukumäärät # * **df.info()** arvojen lukumäärät ja tietotyypit # * **df.describe()** tilastolliset tunnusluvut # * **df.nsmallest()** n pienintä nimetyn sarakkeen suhteen # * **df.nlargest()** n suurinta nimetyn sarakkeen suhteen # * **df[].unique()** nimetyn sarakkeen ainutkertaisten arvojen luettelo # * **pd.crosstab()** lukumäärä- ja prosenttiyhteenvedot # * **df.columns** sarakeotsikot # * **df.index** riviotsikot # * **df.columns.name** sarakeotsikoiden nimi # * **df.index.name** riviotsikoiden nimi # * **df.rename()** sarakeotsikoiden uudelleen nimeämiseen # * **df.reindex()** sarkeotsikoiden tai indeksin uudelleen järjestäminen # * **df.reset_index()** indeksin siirtäminen tavalliseksi sarakkeeksi (tilalle oletusindeksi) # * **df.set_index()** sarakkeen siirtäminen indeksiin # * **df.loc[]** viittaaminen dataframen "viipaleeseen" # * **df.to_frame()** series dataframeksi # * **pd.cut()** arvojen luokittelu # * **df.groupby()** ryhmittely # * **df.T** dataframen transponointi (rivien ja sarakkeiden vaihtaminen päittäin) # # Virhetilanteissa auttaa usein koodisolujen suoritus uudelleen **Run**-valikon komennolla **Run All**. Jos avaat aiemmin aloittamasi muistion, niin ensimmäiseksi kannattaa suorittaa koodisolut **Run**-valikon komennolla **Run All** # ## Lisätietoa # # # Data-analytiikka Pythonilla https://tilastoapu.wordpress.com/python/