#!/usr/bin/env python # coding: utf-8 # In[1]: from datetime import datetime print(f'Päivitetty {datetime.now().date()} / Aki Taanila') # # Kuvaileva analytiikka - parhaat käytänteet # # Oletan, että muistion https://nbviewer.org/github/taanila/kuvaileva/blob/main/pandas1.ipynb sisältö on lukijalle tuttu. # # Tässä muistiossa käytän seuraavia funktioita laskentaan: # # * **crosstab** frekvenssitaulukon laskemiseen # * **cut** määrällisen muuttujan luokitteluun # * **count** lukumäärän laskemiseen # * **sum** summan laskemiseen # * **mean** keskiarvon laskemiseen # * **describe** tilastollisten tunnuslukujen laskemiseen # # Tässä muistiossa käytän seuraavia funktioita kaavioiden luontiin: # # * **plot(kind='bar')** pystypylväskaavio # * **barplot** seaborn-kirjaston pylväskaavio, joka esittää oletuksena keskiarvot, mutta keskiarvon sijasta voin käyttää muitakin funktioita (esimerkiksi **sum**) # * **countplot** seaborn-kirjaston pylväskaavio, joka esittää lukumääriä # * **histplot** seaborn-kirjaston histogrammi, joka esittää luokitellun jakauman # * **boxplot** seaborn-kirjaston kaavio, joka esittää viiden luvun yhteenvedon # * **subplots** useita kaavioita sisältävän kuvion luonti # # Käytän muotoiltuja merkijonoja (**f-string**). F-string alkaa f-kirjaimella, jota seuraa merkkijono. Merkkijonon sisällä voin käyttää Pythonin muuttujien arvoja ja laskukaavoja aaltosulkujen sisällä; esimerkiksi `f'Alennettu hinta {0.90*hinta}'`. Jos viittaan datan muuttujaan f-stringin sisällä, niin käytän muuttujan ympärillä heittomerkkien sijasta lainausmerkkejä, esimerkiksi `f'lukumäärä (n = {df["ikä"].count()})'`. # # # # ## Alkuvalmistelut # In[2]: import pandas as pd # Grafiikkaa varten import matplotlib.pyplot as plt import seaborn as sns sns.set_style('whitegrid') # Kaavioiden prosenttiakselin muotoiluun from matplotlib.ticker import PercentFormatter ticks = PercentFormatter(xmax=100, decimals=0, symbol=' %') # Sanakirja taulukoiden muotoiluun # f-sarakkeeseen 0 desimaalia, %-sarakkeeseen 1 desimaali format = {'f':'{:.0f}', '%':'{:.1f} %'} # In[3]: df = pd.read_excel('https://taanila.fi/data1.xlsx') # Muuttujien tekstimuotoisia arvoja listoina sukup = ['mies', 'nainen'] koulutus = ['peruskoulu', '2. aste', 'korkeakoulu', 'ylempi korkeakoulu'] tyytyväisyys = ['erittäin tyytymätön', 'tyytymätön', 'siltä väliltä', 'tyytyväinen', 'erittäin tyytyväinen'] # ## Frekvenssitaulukko kategoriselle muuttujalle # In[4]: df1 = pd.crosstab(df['koulutus'], 'f') df1.columns.name = '' df1.index = koulutus df1['%'] = df1['f']/df1['f'].sum()*100 # Näytetään taulukko muotoiltuna df1.style.format(format) # ## Frekvenssien graafinen esittäminen # In[5]: # Pystypylväskaavio frekvenssitaulukon frekvensseistä df1['f'].plot(kind='bar', width=0.8, rot=0) # y-akselin otsikko plt.ylabel(f'lukumäärä, n = {df1["f"].sum()}') # poistetaan whitegrid-tyylin tuoma x-akselilta lähtevä pystyviivoitus plt.grid(axis='x') # ## Frekvenssiprosenttien graafinen esittäminen # In[6]: # Kaavio frekvenssitaulukon prosenteista df1['%'].plot(kind='bar', width=0.8, rot=0) plt.ylabel(f'prosenttia, n = {df1["f"].sum()}') # y-akselin lukujen muotoilu prosenteiksi # gca() (get current axis) viittaa luotuun kaavioon plt.gca().yaxis.set_major_formatter(ticks) plt.grid(axis='x') # ## Frekvenssit dikotomisille (dummy) muuttujille # # Datan muuttujat *työterv*, *lomaosa*, *kuntosa* ja *hieroja* ovat dikotomisia (onko käyttänyt kyseistä etuutta?). Arvo 1 tarkoittaa etuuden käyttöä ja ykkösten summana saadaan käyttäjien lukumäärä. # In[7]: dikot = ['työterv', 'lomaosa', 'kuntosa', 'hieroja'] # summat dataframeen ja lopuksi järjestäminen lukumäärän mukaan df2 = df[dikot].sum().to_frame('f').sort_values('f', ascending=False) # Prosentit lasketaan vastaajien lukumäärästä (datan rivien määrä) df2['%'] = df2['f']/df.shape[0]*100 df2.style.format(format) # In[8]: # Muuttujien lista frekvenssien mukaisessa järjestyksessä list = df[dikot].sum().sort_values(ascending=False).index # Seaborn kaaviot tehdään suoraan alkuperäisestä datasta, # joten edellisessä solussa laskettua taulukkoa ei tarvita # fillna-funktio korvaa puuttuvat arvot nollilla sns.barplot(data=df[list].fillna(0), estimator=sum) plt.ylabel('lukumäärä') # ## Puuttuvat havainnot # # Muuttuja *työtov* (tyytyväisyys työtovereihin) mitattiin 5-portaisella asteikolla (1=erittäin tyytymätön, 5=erittäin tyytyväinen). Yhdeltä henkilöltä puuttui vastaus tähän kysymykseen. Kokonaislukutyyppiselle muuttujalla (int) ei sallita puuttuvia arvoja, joten tämä muuttuja on liukulukutyyppinen (float) toisin kuin muut tyytyväisyysmuuttujat. Tämä ei yleensä analysoinnin kannalta aiheuta mitään ongelmia eikä sitä mitenkään erikseen tarvitse huomioida. # # Huomiota vaativa ongelma on se, että kukaan ei ollut *erittäin tyytymätön* työtovereihin eli vastausvaihtoehtoa 1 ei ole kukaan valinnut. Se puuttuu myös frekvenssitaulukosta. Olisi kuitenkin hyvä näyttää kyseinen vaihtoehto frekvenssitaulukossa. # # Tämä onnistuu vaihtamalla muuttujan tyypiksi **category**. # In[9]: # Tässä ei näy arvoa 1.0 (erittäin tyytymätön), koska kukaan ei sitä valinnut df3 = pd.crosstab(df['työtov'], 'f') df3.columns.name = '' df3['%'] = df3['f']/df3['f'].sum()*100 df3.style.format(format) # In[10]: # Teen uuden muuttujan, jonka tyyppi on category df['työtov_cat'] = pd.Categorical(df['työtov'], categories=[1, 2, 3, 4, 5], ordered=True) # In[11]: # dropna=False -parametrin ansiosta myös 'erittäin tyytymätön' tulee frekvenssitaulukkoon mukaan # Joissain versioissa tarvitaan puuttuvien arvojen poisto dropna() df4 = pd.crosstab(df['työtov_cat'].dropna(), 'f', dropna=False) df4.columns.name = '' df4.index = tyytyväisyys df4['%'] = df4['f']/df4['f'].sum()*100 df4.style.format(format) # In[12]: # Kaavio frekvenssitaulukon prosenteista df4['%'].plot(kind='bar', width=0.8, rot=45) plt.ylabel(f'prosenttia, n = {df4["f"].sum()}') plt.gca().yaxis.set_major_formatter(ticks) plt.grid(axis='x') # ## Usean kaavion kuvio # # **For**-toistorakennetta käyttäen voin esittää usean muuttujan jakaumat yhdessä kuviossa, joka sisältää monta kaaviota. # # Komento `fig, axs = plt.subplots(nrows=1, ncols=5, sharey=True, figsize=(12, 3))` luo kuvion, jossa on 5 kaaviota vierekkäin (ncols=5) ja kaavioilla on yhteinen y-akseli (sharey=True). Komento palauttaa sekä kuvion (fig) että sen sisältämien kaavioiden listan (axs). # # **Enumerate**-funktio palauttaa sekä tyytyväisyydet-listan muuttujien järjestysnumerot (i) että muuttujien nimet (var). Järjestysnumeroita tarvitaan osoittamaan mihin kaavioon mikäkin muuttuja sijoitetaan (`ax=axs[i]`). # In[13]: tyytyväisyydet = ['johto', 'työtov_cat', 'työymp', 'palkkat', 'työteht'] fig, axs = plt.subplots(nrows=1, ncols=5, sharey=True, figsize=(12, 3)) for i, var in enumerate(tyytyväisyydet): sns.countplot(data=df, x=var, ax=axs[i]) axs[i].set_ylabel('') # Otsikko ensimmäisen kaavion y-akselille axs[0].set_ylabel('lukumäärä') # ## Luokiteltu jakauma # # Määrällisen muuttujan voin luokitella pandas-kirjaston **cut**-funktiolla. Oletuksena luokat eivät sisällä luokan alarajaa, mutta sisältävät ylärajan. # # **Histplot**-funktio tuottaa luokituksen, jossa luokat sisältävät alarajan, mutta eivät ylärajaa. # # Jos haluan cut-funktion tuottamien luokkien olevan yhdenmukaisia histogrammin kanssa, käytän cut-funktion yhteydessä parametria `right=False`. Tämänkin jälkeen voi tulla eroa viimeisen luokan ylärajan kohdalla, joka histogrammissa sisältyy luokkaan. # In[14]: bins = [19, 29, 39, 49, 59, 69] df['ikäluokka'] = pd.cut(df['ikä'], bins=bins, right=False) df5 = pd.crosstab(df['ikäluokka'], 'f') df5.columns.name = '' df5['%'] = df5['f']/df5['f'].sum()*100 df5.style.format(format) # In[15]: # Histogrammi luokitellun jakauman havainnollistamiseen sns.histplot(df['ikä'], bins=bins) plt.ylabel(f'lukumäärä, n = {df["ikä"].count()}') # x-akselille luokkien rajakohdat plt.xticks(bins) # In[16]: # Prosentit histplot-kaavioon sns.histplot(df['ikä'], stat='percent', bins=bins) plt.xlabel('ikä') plt.ylabel(f'%, n = {df["ikä"].count()}') plt.xticks(bins) plt.gca().yaxis.set_major_formatter(ticks) # ## Tilastolliset tunnusluvut # # Tilastolliset tunnusluvut voin laskea **describe**-funktiolla. # # Seabornin **boxplot** (ruutu- ja janakaavio) on hyvä tapa havainnollistaa tunnuslukuja (pienin, alaneljännes, mediaani, yläneljännes, suurin). # # Usean muuttujan keskiarvojen havainnollistamiseen sopii seabornin **barplot**, joka esittää oletuksena keskiarvot pylväinä ja näyttää myös virhemarginaalit. # In[17]: # Palkan tunnuslukuja df['palkka'].describe() # In[18]: # Ruutu- ja janakaavio palkalle sns.boxplot(data=df, x='palkka') # In[19]: # Tunnuslukuja tyytyväisyyksille df.loc[:, 'johto':'työteht'].describe() # In[20]: # Tyytyväisyysmuuttujien lista keskiarvon mukaisessa järjestyksessä list1 = df.loc[:,'johto':'työteht'].mean().sort_values().index # Keskiarvoja kuvaavat pylväät ja virhemarginaalit sns.barplot(data=df[list1], orient='h') # x-akselin skaalaus (pienin arvo 1, suurin arvo 5) plt.xlim(1, 5) plt.xlabel('tyytyväisyyskeskiarvo (5 = erittäin tyytyväinen)') # In[21]: # Avaan vielä toisen datan, jossa asteikolla 0-10 mitattuja mielikuvia hatco = pd.read_excel('https://taanila.fi/hatco.xlsx') hatco # In[22]: # Pelkästään mielikuvamuuttujat hatco1 = hatco.loc[:, 'Delivery Speed':'Satisfaction Level'] # In[23]: # Tunnuslukuja mielikuva-muuttujille hatco1.describe() # In[24]: # Mielikuvamuuttujat keskiarvojen mukaisessa järjestyksessä list2 = hatco1.mean().sort_values().index # Keskiarvot ja virhemarginaalit sns.barplot(data=hatco1[list2], orient='h') plt.xlabel('mielikuva (0 = huono, 10 = erinomainen)') plt.xlim(0, 10) # In[25]: # Ruutu- ja janakaavio mielikuvamuuttujista # Ruutu- ja janakaavio sisältää enemmän informaatiota kuin pelkkien keskiarvojen esittäminen! sns.boxplot(data=hatco1[list2], orient='h') plt.xlabel('mielikuva (0 = huono, 10 = erinomainen)') plt.xlim(0, 10) # # ## Kaavioiden värit ja koot # # Kaavion luonnissa voin määrittää värin **color**-parametrin arvona (toimii myös seaborn-kaavioille). Esimerkiksi tämän muistion ensimmäiseen kaavioon saan pylväille vihreän värin seuraavasti: `df1['f'].plot.bar(width=0.8, rot=0, color='green')`. Reunavärin voin määrittää **edgecolor**-parametrilla. # # Värejä: https://matplotlib.org/stable/gallery/color/named_colors.html # # Kaavion luonnissa voit määrittää käytettävän värikartan suoraan dataframesta tehdyille kaavioille **cmap**-parametrilla ja seaborn-kaavioille **palette**-parametrilla. Esimerkiksi tämän muistion viimeisessä kaaviossa voin vaihtaa värikartaksi Greens: `sns.boxplot(data=hatco1[list2], orient='h', palette='Greens')` # # Värikarttoja: https://matplotlib.org/stable/tutorials/colors/colormaps.html # # Suoraan dataframesta tehdyn kaavion koon voin määrittää **figsize**-parametrin arvona. Esimerkiksi `df1['f'].plot.bar(width=0.8, rot=0, figsize=(10, 6))` # # Seaborn-kaavion koon voin määrittää luomalla kaaviota ympäröivän kuvion (figure) ennen kaavion luomista. Esimerkiksi `plt.figure(figsize=(10,6))` # ## Lisätietoa # # Data-analytiikka Pythonilla https://tilastoapu.wordpress.com/python/