Analiza danych dotyczących Irysów¶

Zbiór danych, którym dysponujemy zawiera informacje o trzech gatunkach irysów: Iris setosa, Iris versicolor, i Iris virginica. Dane obejmują pomiary czterech cech: długości i szerokości działki kielicha oraz długości i szerokości płatka kwiatu. Każdy wiersz w zbiorze danych reprezentuje pojedynczy kwiat, a wartości pomiarów są podane w centymetrach.

Na początek dobrze byłoby się zorientować, o czym w ogóle mówimy. Czym są owe "irysy"? Otóż irys (w języku polskim: kosaciec) to roślina ozdobna strefy umiarkowanej, czyli występuje również w Polsce. Jest blisko spokrewniona z szafranem, frezją i mieczykiem. Ponieważ jest mało wymagająca, bardzo łatwo ją hodować. Jest ceniona z powodu swoich ciekawych i efektownych kwiatów. Używana m.in. w przemyśle perfumeryjnym i ziołolecznictwie. Fotografia poniżej pokazuje typowego przedstawiciela gatunku.

Iris-pojedynczy.jpg

Na świecie istnieje kilkaset gatunków irysa. My dzisiaj zajmiemy się tylko trzema z nich: Iris setosa, Iris versicolor, i Iris virginica:

irysy-zdjecia.png

Patrząc na nie okiem laika, muszę przyznać, że rzeczywiście wyglądają dość ładnie i mogą być ozdobą każdego ogrodu. I z tego, co widzę, potrzeba botanika-profesjonalisty, żeby je od siebie odróżnić.

Wracając do naszych liczb, dysponujemy zbiorem danych, który zawiera 150 rekordow. Każdy rekord to dane jednej rośliny. Dane te, to pomiary w centymetrach czterech cech: długości i szerokości płatka kwiatu - petal oraz długości i szerokości działki kielicha - sepal. I znowu, abyśmy wiedzieli o czym mówimy: płatki kwiatu - petal to elementy, ktore znajdują się wyżej, pachną i ich funkcją jest przyciąganie owadów zapylających. Z kolei działki kielicha - sepal, znajdują się poniżej. Są twardsze i bardziej sztywne od płatkow. Ich zadaniem w początkowej fazie jest ochrona pączka kwiatowego. Zaś po otwarciu kwiatu, podtrzymują jego płatki w górze, same wyginając się ku dołowi. Poniższy obraz wyjaśnia sytuację:

irys-wymiary2.png

In [2]:
# sekcja importowa

import pandas as pd
import os
import itertools
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
In [3]:
# wczytanie bazy danych, tworzenie DataFrame

df = pd.read_csv('25__iris.csv', sep=",", encoding='utf8')

Hipoteza:¶

Czy istnieje jakiś związek pomiędzy gatunkiem irysa, a wielkością jego kielicha lub platka? Sprawdzimy to analizując posiadany zbiór danych.


1.1 Wnioski płynące z analizy podstawowych informacji o danych:¶

Nasza baza zawiera 150 rekordów, po 50 na każdy gatunek irysa. Oprócz kolumny klasa (gatunek irysa), mamy 4 kolumny zawierające dane numeryczne: długość kielicha (sepal length), szerokość kielicha (sepal width), długość płatka (petal length) i szerokość płatka (petal width). Wielkości te podane są w centymetrach, z dokładnością do 1 milimetra. Dane są kompletne, nie ma wartości brakujących, nie ma potrzeby ich uzupełniania.

Patrząc na podstawowe statystyki, momentalnie widzimy gigantyczną rozpiętość (25-krotną różnicę) pomiędzy wartością minimalną i maksymalną szerokości płatka kwiatowego (petal width). Rzut oka na pierwszy i trzeci kwartyl potwierdza, że nie jest to anomalia pojedynczych danych, ale potężna prawidłowość. Co więcej, bardzo duże odchylenie standardowe mówi nam, że dane w tym przypadku są mocno rozproszone. Przyjrzymy się im zatem później bardziej szczegółowo.

Również duże, choć już nie tak gargantuiczne, bo tylko kilkukrotne różnice możemy dostrzec w przypadku długości płatka (petal length). Jeśli natomiast chodzi o sam kielich, to tutaj dane są dużo bardziej skupione, a różnice niewielkie. Czyli nawet małe kwiaty, z małymi płatkami, są chronione przez relatywnie duże kielichy. Co oznacza, że dopuki dany kwiat się nie otworzy, nie będziemy mieli pojęcia o wielkości jego płatków.

1.2 Analiza podstawowych informacji o danych:¶

In [3]:
# podstawowe informacje o DataFrame
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150 entries, 0 to 149
Data columns (total 5 columns):
 #   Column                            Non-Null Count  Dtype  
---  ------                            --------------  -----  
 0   długość kielicha (sepal length)   150 non-null    float64
 1   szerokość kielicha (sepal width)  150 non-null    float64
 2   długość płatka (petal length)     150 non-null    float64
 3   szerokość płatka (petal width)    150 non-null    float64
 4   klasa (class)                     150 non-null    object 
dtypes: float64(4), object(1)
memory usage: 6.0+ KB
In [4]:
# lista kolumn
df.columns
Out[4]:
Index(['długość kielicha (sepal length)', 'szerokość kielicha (sepal width)',
       'długość płatka (petal length)', 'szerokość płatka (petal width)',
       'klasa (class)'],
      dtype='object')
In [5]:
# kilka losowych rekordow, zeby zorientowac sie z czym mamy do czynienia
df.sample(5)
Out[5]:
długość kielicha (sepal length) szerokość kielicha (sepal width) długość płatka (petal length) szerokość płatka (petal width) klasa (class)
55 5.7 2.8 4.5 1.3 Iris-versicolor
99 5.7 2.8 4.1 1.3 Iris-versicolor
78 6.0 2.9 4.5 1.5 Iris-versicolor
57 4.9 2.4 3.3 1.0 Iris-versicolor
121 5.6 2.8 4.9 2.0 Iris-virginica
In [6]:
# sprawdzenie ilosci unikatowych wartosci
df.nunique()
Out[6]:
długość kielicha (sepal length)     35
szerokość kielicha (sepal width)    23
długość płatka (petal length)       43
szerokość płatka (petal width)      22
klasa (class)                        3
dtype: int64
In [7]:
# sprawdzenie brakujacych wartosci
df.isnull().sum()
Out[7]:
długość kielicha (sepal length)     0
szerokość kielicha (sepal width)    0
długość płatka (petal length)       0
szerokość płatka (petal width)      0
klasa (class)                       0
dtype: int64
In [8]:
# sprawdzenie liczby rekordow dla kazdego gatunku irysa
df['klasa (class)'].value_counts()
Out[8]:
Iris-setosa        50
Iris-versicolor    50
Iris-virginica     50
Name: klasa (class), dtype: int64
In [9]:
# podstawowe statystyki
df.describe().round(2).T
Out[9]:
count mean std min 25% 50% 75% max
długość kielicha (sepal length) 150.0 5.84 0.83 4.3 5.1 5.80 6.4 7.9
szerokość kielicha (sepal width) 150.0 3.05 0.43 2.0 2.8 3.00 3.3 4.4
długość płatka (petal length) 150.0 3.76 1.76 1.0 1.6 4.35 5.1 6.9
szerokość płatka (petal width) 150.0 1.20 0.76 0.1 0.3 1.30 1.8 2.5

2.1 Wnioski z analizy pojedynczych zmiennych:¶

Analizując posiadane dane, dla każdego z gatunków oddzielnie, od razu widzimy, że gatunek Iris-setosa ma malutkie płatki kwiatu, mieszczące się w bardzo wąskich zakresach. Zarówno jeśli chodzi o ich długość, jak i szerokość. Dodatkowo, jego najdłuższe płatki są o przeszło 50% krótsze od najkrótszych spośród dwóch pozostałych gatunków. Jeśli zaś chodzi o szerokość płatków, to tu różnice są jeszcze większe i sięgają niemal 100%. Na poniższych wykresach widać to od razu. Zatem dosyć łatwo przyjdzie nam wyodrębnić ten gatunek na podstawie pomiaru wielkości jego płatków kwiatowych (petal). Możemy zatem przyjąć, że irys o płatkach kwiatu o długości mniejszej niż 2.5 cm i szerokości mniejszej niż 0.9 cm należy z pewnością do gatunku Irys-setosa.

Potwierdza to częściowo naszą hipotezę o możliwości rozróżnienia gatunków dzięki pomiarom wielkości ich kwiatów. Niestety, w przypadku dwóch pozostałych gatunków (Irys-versicolor i Iris-virginica), sprawa nie jest już tak oczywista. Patrząc na wykresy poniżej możemy jednak zauważyć, że gatunek Iris-virginica góruje zdecydowanie nad swoim kuzynem jeśli chodzi o długość i szerokość płatka kwiatowego.

2.2 Analiza pojedynczych zmiennych:¶

In [10]:
# pogrupowanie danych wedlug gatunkow
pd.set_option('max_colwidth', None)
grouped = df.groupby('klasa (class)', as_index=False)

statystyki_gatunkami = grouped.agg(['mean', 'std', 'min', 'max']).round(2)
statystyki_gatunkami
Out[10]:
długość kielicha (sepal length) szerokość kielicha (sepal width) długość płatka (petal length) szerokość płatka (petal width)
mean std min max mean std min max mean std min max mean std min max
klasa (class)
Iris-setosa 5.01 0.35 4.3 5.8 3.42 0.38 2.3 4.4 1.46 0.17 1.0 1.9 0.24 0.11 0.1 0.6
Iris-versicolor 5.94 0.52 4.9 7.0 2.77 0.31 2.0 3.4 4.26 0.47 3.0 5.1 1.33 0.20 1.0 1.8
Iris-virginica 6.59 0.64 4.9 7.9 2.97 0.32 2.2 3.8 5.55 0.55 4.5 6.9 2.03 0.27 1.4 2.5
In [11]:
# tworzenie macierzy wykresow z 1 wierszem i 4 kolumnami
fig, axes = plt.subplots(1, 4, figsize=(18, 4))
columns = df.columns[0:4]

# rysowanie linii KDE
for i, col in enumerate(columns):
    sns.kdeplot(data=df, x=col, hue=df.columns[4], ax=axes[i])
No description has been provided for this image

Hipoteza¶

Może da się rozróżnić dwa pozostałe gatunki irysa dzięki jakimś zależnościom między posiadanymi przez nas danymi? Na przykład zupełnie różna dla obu gatunków może być proporcja długości płatka do jego szerokości? Dlatego w następnym kroku przyjrzymy się wszelkim zależnościom pomiędzy danymi.


3.1 Wnioski z analizy zależności pomiędzy danymi:¶

Dotychczas doszliśmy do tego, jak wyodrębnić gatunek Iris-setosa z posiadanej próbki. Ma on bowiem tak małe płatki kwiatowe, że wystarczy w tym przypadku zastosować jedynie proste kryterium wielkości. Możemy zatem podczas dalszej analizy skupić się na dwóch pozostałych gatunkach irysa.

Zacząłem analizę zależności pomiędzy danymi trochę nietypowo, bo od sprawdzenia wartości odstających. Chciałem bowiem zorientować się, czy to czasem właśnie nie te wartości zaburzają nam nasze postrzeganie posiadanych danych. Jednak rzut oka na boxploty wykazał, że wartości odstające nie mają tu nic do rzeczy. Zarówno wielkości płatków jak i kielicha dla gatunków Iris-versicolor i Iris-virginica są dość zbliżone do siebie. Przy czym, jeśli chodzi o parametry kielicha, to wartości te są tak mocno przemieszane, że ewentualna ich separacja jest raczej malo prawdopodobna. Za to w przypadku płatków widać możliwe pole do działania.

Żeby mieć jednak całkowitą pewność, wygenerowałem scatterploty dotyczące wszystkich możliwych par danych. Z obrazów które otrzymaliśmy, jasno widać, że najbardziej obiecującą parą jest: długość płatka (petal length) i szerokości płatka (petal width). Powiększyłem więc ten obraz usuwając z niego dla czytelności gatunek Iris-setosa. Od razu rzuciła mi się w oczy możliwość zakreślenia obszarów zajmowanych tylko przez jeden gatunek. Wybrałem najprostszy możliwy wariant - prostokąt.

Arbitralnie ustaliłem jego granice na: od 0 do 1.7 cm dla szerokości płatka i od 0 do 5.1 cm dla długości płatka. W tym obszarze mieszczą się wszystkie próbki dla Iris-versicolor i jedynie 3 egzemplarze Iris-virginica. Uznałem, że taki ok. 5% błąd dla jednego gatunku i zerowy dla drugiego, jest dość obiecujący. Oczywiście przypominam, że operujemy jedynie na próbce 150 osobników. Możliwe, że w przypadku większych ilości, trzeba będzie zmodyfikować granice wydzielonego przez nasz obszaru.

3.2 Analiza zależności pomiędzy danymi:¶

In [12]:
# tworzenie macierzy wykresow z 1 wierszem i 4 kolumnami

fig, axes = plt.subplots(1, 4, figsize=(18, 4))
columns = df.columns[0:4]
fig.suptitle('Boxploty poszczegolnych cech kwiatu podzielone na klasy (gatunki)', fontsize=20)

# rysowanie boxplotow
for i, col in enumerate(columns):
       sns.boxplot(x='klasa (class)', y=col, data=df, ax=axes[i])
No description has been provided for this image
In [13]:
# tworzenie wszystkich mozliwych scatterplotow, zeby zobaczyc jak wyglada rozklad danych

# zmienna do sledzenia, ktory subplot program aktualnie rysuje
current_subplot = 1
hue = df['klasa (class)']

# tworzenie macierzy wykresow
fig, axs = plt.subplots(2, 6, figsize=(18, 6))

# rysowanie scatterplotow
for i, j in itertools.product(range(len(columns)), range(len(columns))):
    if i != j:
        ax = axs[(current_subplot - 1) // 6, (current_subplot - 1) % 6]
        sns.scatterplot(data=df, x=columns[j], y=columns[i], hue=hue, ax=ax, legend=False)
        current_subplot += 1
        if current_subplot > 12:
            break
    if current_subplot > 12:
        break
        
plt.tight_layout()
plt.show()
No description has been provided for this image
In [9]:
# tworzenie jednego, duzego wykresu scatterplot z wybrana para danych

plt.figure(figsize=(8, 6))

# odsiewanie z bazy 'Iris-setosa'
filtered_df = df[~df['klasa (class)'].isin(['Iris-setosa'])]

# agresywne i mocno kontrastowe kolorki
colors = {'Iris-versicolor': 'red', 'Iris-virginica': 'green'}

# rysowanie wykresu
plt.scatter(filtered_df['szerokość płatka (petal width)'], 
            filtered_df['długość płatka (petal length)'],  
            c=filtered_df['klasa (class)'].map(colors), 
            label=filtered_df['klasa (class)'])

# etykiety i tytul
plt.xlabel('Szerokość płatka (petal width)')
plt.ylabel('Długość płatka (petal length)')
plt.title('Scatter Plot: Szerokość płatka (petal width) i Długość płatka (petal length)')
plt.legend(handles=[plt.Line2D([0], [0], marker='o', color='w', markerfacecolor=color, markersize=10, label=label) for label, color in colors.items()])
plt.grid(True)

# pionowy odcinek od osi x dla wartosci 1.7 do wysokosci 5.1
plt.axvline(x=1.7, ymin=0, ymax=(5.1-plt.ylim()[0])/(plt.ylim()[1]-plt.ylim()[0]), color='black', linestyle='--')

# poziomy odcinek od osi y na wysokosci 5.1 do wartosci 1.7
plt.axhline(y=5.1, xmin=0, xmax=(1.7-plt.xlim()[0])/(plt.xlim()[1]-plt.xlim()[0]), color='black', linestyle='--')

plt.show()
No description has been provided for this image
In [15]:
# policzenie ilosci blednie zaklasyfikowanych danych

df[(df['długość płatka (petal length)'] <= 5.1) & (df['szerokość płatka (petal width)'] <= 1.7) & (df['klasa (class)'] == 'Iris-virginica')].value_counts()
Out[15]:
długość kielicha (sepal length)  szerokość kielicha (sepal width)  długość płatka (petal length)  szerokość płatka (petal width)  klasa (class) 
4.9                              2.5                               4.5                            1.7                             Iris-virginica    1
6.0                              2.2                               5.0                            1.5                             Iris-virginica    1
6.3                              2.8                               5.1                            1.5                             Iris-virginica    1
dtype: int64

4.1 Końcowe wnioski z analizy danych:¶

Dysponując zbiorem danych zawierającym informacje o trzech gatunkach irysow: Iris setosa, Iris versicolor, i Iris virginica (150 rekordów, po 50 na każdy gatunek), przy czym dane te obejmowały jedynie cztery cechy: długość i szerokość działki kielicha oraz długość i szerokość płatka kwiatu (z dokładnością do 0.1 cm), gdzie każdy rekord reprezentował pojedynczy kwiat, udowodniliśmy, że da się rozróżnić kwiaty z próbki na poszczególne gatunki.

I tak, gatunek Iris-setosa ma płatki kwiatu o długości <1 cm, 1.9 cm> i szerokości <0.1 cm, 0.6 cm>, Iris-versicolor ma płatki o długości <3 cm, 5.1 cm> i szerokości <1 cm, 1.7 cm> zaś pozostałe egzemplarze należą do gatunku Iris-virginica.

Przyjmując dość arbitralnie graniczną długość płatka na 5.1 cm oraz szerokość na 1.7 cm odseparowaliśmy 100% przedstawicieli gatunku Iris-versicolor. Jednak w tych wielkościach znalazło się niestety również trzech przedstawicieli gatunku Iris-virginica. Jednak błąd wielkości ok. 5% przy tak uproszczonej weryfikacji, powinien być uważany za całkowicie dopuszczalny. Dodatkowo, w toku analizy okazało się, że wielkość kielicha kwiatu nie ma żadnego znaczenia dla rozróżnienia międzygatunkowego, gdyż jest dla wszystkich gatunków praktycznie identyczna.

Niniejszym postawiona na wstępie hipoteza o możliwości rozróżnienia gatunków zostaje uznana za udowodnioną. QED

Na zakończenie, poniżej, zaprezentowano schematyczne rysunki wszystkich trzech gatunków. Teraz od razu widać występujące między nimi różnice :-)

irisy-schematy.jpg

In [10]:
!jupyter nbconvert 25__zadanie_domowe__modul_4_1_RafalNey.ipynb --to slides --no-input --no-prompt
[NbConvertApp] Converting notebook 25__zadanie_domowe__modul_4_1_RafalNey.ipynb to slides
[NbConvertApp] WARNING | Alternative text is missing on 2 image(s).
[NbConvertApp] Writing 1718066 bytes to 25__zadanie_domowe__modul_4_1_RafalNey.slides.html
In [ ]: