article banner

Analiza danych w Pythonie

Cześć! To jest fragment książki Python od podstaw, która ma pomóc w nauce programowania od zera. Znajdziesz ją na Allegro, w Empikach i w księgarniach internetowych.

Wyobraź sobie, że projektujesz nowy wielki szpital. Musisz podjąć decyzję, ile powinno być gabinetów zabiegowych różnego typu, jakie maszyny do badań zamówić, ilu i jakiej specjalizacji lekarzy zatrudnić. Chodzi o ogromne pieniądze, nie chcemy zmarnować ani złotówki. Jak więc odpowiedzialnie podjąć takie decyzje? Przede wszystkim należy oprzeć się na danych. Jeśli mamy dane z innych szpitali, możemy policzyć, ilu przypadków różnego typu będziemy się spodziewać, a w związku z tym ile różnego typu sprzętu i ilu specjalistów będziemy potrzebować.

Dane i ich analiza

Dane są bardzo ważne w niemal każdej dziedzinie. Reporterzy i influencerzy patrzą na liczbę wyświetleń ich artykułów, by dowiedzieć się, które ich posty cieszyły się większą popularnością. Serwisy takie jak Twitter czy Facebook patrzą na zachowanie użytkowników, by dostosować lepiej treści i sam portal. Firmy ubezpieczeniowe zbierają dane na temat zdarzeń, dzięki czemu lepiej mogą wyceniać składki i szkody. Nawet kawiarnie zbierają dane na temat sprzedaży, dzięki czemu kawy stają się coraz smaczniejsze1.

Aby to było możliwe istotne jest zbieranie danych. To większość firm już robi. Następnie istotne jest ich przetwarzanie. Dla przykładu wyobraźmy sobie, że chcemy usprawnić naszą służbę zdrowia i dzięki umowom międzynarodowym dostaliśmy dane o niemieckich pacjentach. Są one jednak w zupełnie innym formacie niż nasze. Do opisu chorób używane są inne kody, wszystkie atrybuty są inaczej ułożone i opisane. Aby współgrały z naszymi, potrzebujemy je sprowadzić do podobnego formatu2. Tutaj wchodzimy w dziedzinę przetwarzania danych (nazywaną także data engineering). Wreszcie pozostaje wyciąganie wniosków z danych, czyli analityka. To ona stanowi sedno pracy analityków czy naukowców.

Analiza danych z Pythonem

Współcześnie Python stał się właściwie standardem przy przetwarzaniu i analizie danych. W dużym stopniu dzięki wyśmienitym pakietom, takim jak Pandas, SciPy czy NumPy. Za niedługo przekonamy się o ich przydatności. Wcześniej jednak pokażę, że i bez nich wiele można zrobić. Do analizy użyjemy zaś bardzo typowego i często wykorzystywanego na kursach i uczelniach zbioru danych o pasażerach statku Titanic. Jest on bardzo popularny nie bez powodu: jest niewielki, prosty i zrozumiały. Dlatego my również od niego zaczniemy.

Dane o losie pasażerów statku Titanic z tragicznego kursu można znaleźć na różnych stronach internetowych, w tym między innymi pod adresem kt.academy/titanic.csv na stronie Kt. Academy. Są one zapisane w formacie CSV: każda linijka to inny pasażer, a kolejne kolumny z danymi oddzielone są przecinkiem. Z wyjątkiem pierwszej linii, zwanej nagłówkiem, w której znajdują się nazwy kolejnych kolumn. Bardzo przypomina to tabelki w programach takich jak Excel (zresztą pliki CSV można otworzyć w Excelu).

Aby odczytać te dane, użyjemy wbudowanego pakietu csv. Nie trzeba go instalować, wystarczy zaimportować. Odczytamy plik przy użyciu wbudowanej funkcji open, a następnie użyjemy csv.reader, by odczytać kolejne linie, oraz listę składaną, by zamienić plik na listę z danymi. Na koniec oddzielmy nagłówek od rzeczywistych danych.

import csv

file = open("titanic.csv")
data = [row for row in csv.reader(file)]
file.close()
header = data[0]
data = data[1:]

Zobaczmy ile mamy pasażerów:

rows_num = len(data)
print(rows_num)  # 1313

Wikipedia podaje, że powinno być 1316 pasażerów, a więc różnica to tylko 3 osoby. To dobry wynik. Zobaczmy teraz, jak wyglądają nasze dane:

print(header)
# ['Name', 'PClass', 'Age', 'Sex', 'Survived']
print(data[0])
# ['Allen, Miss Elisabeth Walton', '1st', '29',
# 'female', '1']
print(data[1])
# ['Allison, Miss Helen Loraine', '1st', '2',
# 'female', '0']
print(data[2])
# ['Allison, Mr Hudson Joshua Creighton', '1st', '30',
# 'male', '0']

Zauważ, że wszystkie wartości odczytane przez csv.reader są typu string. Bardziej zaawansowane pakiety (takie jak pandas) potrafią rozróżnić typ wartości.

Po tytułach kolumn i przykładowych wartościach widzimy, jak wyglądają nasze dane. Znamy pełne imię, klasę biletu (1st, 2nd, 3rd), wiek, płeć (male i female) oraz to, czy pasażer przeżył (1 czyli tak, oraz 0 czyli nie).

W procesie analizy danych często stawiamy sobie kolejne pytania i poszukujemy na nie odpowiedzi. Ciekawym pytaniem może być na przykład, czy klasa biletu wpłynęła na szanse na przeżycie. Na zdrowy rozum, każdy powinien mieć równy dostęp do kabin ratunkowych. Łatwo się można jednak domyślić, że zapewne tak nie było. Sprawdźmy więc.

Aby wybrać osoby z konkretnej klasy, możemy użyć listy składanej z częścią if.

class_1 = [row for row in data if row[1] == '1st']
print(len(class_1))  # 322
class_2 = [row for row in data if row[1] == '2nd']
print(len(class_2))  # 279
class_3 = [row for row in data if row[1] == '3rd']
print(len(class_3))  # 711

Aby policzyć jaki procent przeżył, potrzebujemy policzyć liczbę ocalałych i podzielić przez liczbę wszystkich osób. Tak będzie to wyglądało dla pierwszej klasy:

class_1_survived = [row for row in class_1
                    if row[4] == '1']
print(len(class_1_survived) / len(class_1))
# 0.59...

Aby jednak ułatwić te obliczenia, napisałem funkcję, która za nas policzy i wyświetli procent ocalałych w danym zbiorze:

def print_survival_rate(data):
    count = 0
    survived = 0
    for row in data:
        count += 1
        if row[4] == '1':
            survived += 1
    print(float(survived) / count)


print_survival_rate(class_1)  # 0.59...
print_survival_rate(class_2)  # 0.42...
print_survival_rate(class_3)  # 0.19...

Wygląda na to, że na Titanicu klasa biletu istotnie wpłynęła na szanse na przeżycie. Pasażerowie pierwszej klasy przeżyli w 59%, a ci z trzeciej tylko w 19%. Idąc tym tropem, sprawdźmy, czy jest zależność między pozostałymi kolumnami a przeżyciem. Na filmach w momencie tragedii na morzu często rozbrzmiewa hasło "Najpierw kobiety i dzieci". Zobaczmy, czy na Titanicu respektowano tę zasadę. Zacznijmy od płci.

males = [row for row in data if row[3] == 'male']
print(len(males))  # 851
print_survival_rate(males)  # 0.16...

females = [row for row in data if row[3] == 'female']
print(len(females))  # 462
print_survival_rate(females)  # 0.66...

4-krotnie większe szanse na przeżycie sugerują, że kobiety rzeczywiście zostały puszczone przodem. Co z dziećmi? Za dziecko uznam osoby do 15 roku życia. Niestety w naszych danych brakuje informacji o wieku wielu osób, więc policzymy statystykę dla tych, których wiek znamy.

kids = [row for row in data if row[2] != '' and
        float(row[2]) <= 15]
print(len(kids))  # 73
print_survival_rate(kids)  # 0.64...

adults = [row for row in data if row[2] != '' and
          float(row[2]) > 15]
print(len(adults))  # 683
print_survival_rate(adults)  # 0.38...

Niemal dwukrotnie większa szansa na przeżycie u dzieci sugeruje, że stara morska zasada była respektowana.

Te wszystkie wnioski można znacznie łatwiej uzyskać przy użyciu pakietu pandas. Nie będę tłumaczył tych operacji, chcę tylko pokazać, jak łatwo przeprowadza się analizę, gdy dobrze zna się ten pakiet.

from pandas import read_csv

titanic_df = read_csv("titanic.csv")

# Liczba osób w klasach
class_counts = titanic_df["PClass"]\
    .value_counts()\
    .sort_index()
print(class_counts)
# 1st    322
# 2nd    279
# 3rd    711

# Przeżywalność w klasach
print(titanic_df\
      .groupby("PClass")["Survived"]\
      .mean())
# 1st    0.599379
# 2nd    0.426523
# 3rd    0.194093

# Liczba osób wg płci
print(titanic_df["Sex"]\
      .value_counts()\
      .sort_index())
# female    462
# male      851

# Przeżywalność wg płci
print(titanic_df\
      .groupby("Sex")["Survived"]\
      .mean())
# female    0.666667
# male      0.166863

# Przeżywalność wg klasy i płci
print(titanic_df\
      .groupby(["PClass", "Sex"])["Survived"]\
      .mean())
# PClass  Sex  
# 1st     female    0.937063
#         male      0.329609
# 2nd     female    0.878505
#         male      0.145349
# 3rd     female    0.377358
#         male      0.116232

# Przeżywalność wg wieku
from pandas import cut

titanic_df['AgeGroup'] = cut(
    titanic_df['Age'],
    bins=[i * 10 for i in range(8)]
)
print(titanic_df\
      .groupby("AgeGroup")["Survived"]\
      .mean())
# AgeGroup
# (0, 10]     0.672727
# (10, 20]    0.427350
# (20, 30]    0.330769
# (30, 40]    0.446667
# (40, 50]    0.423077
# (50, 60]    0.500000
# (60, 70]    0.263158

Zakończenie

Sprawny analityk mający dostęp do dobrych danych jest w stanie dowiedzieć się na ich podstawie naprawdę dużo. Takie wnioski są bardzo istotne przy podejmowaniu decyzji, a więc duże firmy, takie jak Google czy Allegro, zatrudniają całe sztaby analityków i specjalistów od danych. Analiza danych jest również kluczowa w nauce i technice. Praktycznie każde laboratorium prowadzące badania naukowe wymaga wsparcia osoby znającej się na analizie danych. Mam nadzieję, że po tym rozdziale masz już choćby mgliste pojęcie, na czym ona polega.

1:

W wielu firmach stosuje się technikę znaną jako testy A/B. Polega ona na tym, że jak rozważamy pewną zmianę, na przykład w procesie robienia kawy, to wprowadzamy ją w losowych miejscach i patrzymy, jak wpłynęła ona na istotne dla nas parametry, na przykład dane sprzedażowe lub zadowolenie klientów.

2:

Format określa, jak zapisane są dane. Dla przykładu data może być zapisana jako 7 Lip 2020, jako 07.03.2020 czy też jako 03-07-2020. To cały czas ta sama data, ale zapisana jest w różnych formatach.