Titanic - analiza danych¶
Wstęp - o danych¶
Dane o pasażerach Titanica
Zbiór danych zawiera informacje o pasażerach RMS Titanic, który zatonął 15 kwietnia 1912 roku po zderzeniu z górą lodową. Dane obejmują takie atrybuty jak klasa podróży, wiek, płeć, liczba rodzeństwa/małżonków na pokładzie, liczba rodziców/dzieci na pokładzie, cena biletu oraz miejsce zaokrętowania.
Zbiór zawiera także informację o tym, czy pasażer przeżył katastrofę.
Titanic przewoził ponad 2,200 osób, z czego ponad 1,500 zginęło, co czyni tę katastrofę jedną z najbardziej tragicznych w historii morskiej.
Kolumny:
- pclass - Klasa biletu
- survived - Czy pasażer przeżył katastrofę
- name - Imię i nazwisko pasażera
- sex - Płeć pasażera
- age - Wiek pasażera
- sibsp - Liczba rodzeństwa/małżonków na pokładzie
- parch - Liczba rodziców/dzieci na pokładzie
- ticket - Numer biletu
- fare - Cena biletu
- cabin - Numer kabiny
- embarked - Port, w którym pasażer wszedł na pokład (C = Cherbourg, Q = Queenstown, S = Southampton)
- boat - Numer łodzi ratunkowej
- body - Numer ciała (jeśli pasażer nie przeżył i ciało zostało odnalezione)
- home.dest - Miejsce docelowe
1. Ogólny przegląd danych¶
Wczytanie danych z pliku do analizy¶
import pandas as pd
df = pd.read_csv('26__titanic.csv', sep=",")
df
| pclass | survived | name | sex | age | sibsp | parch | ticket | fare | cabin | embarked | boat | body | home.dest | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1.0 | 1.0 | Allen, Miss. Elisabeth Walton | female | 29.0000 | 0.0 | 0.0 | 24160 | 211.3375 | B5 | S | 2 | NaN | St Louis, MO |
| 1 | 1.0 | 1.0 | Allison, Master. Hudson Trevor | male | 0.9167 | 1.0 | 2.0 | 113781 | 151.5500 | C22 C26 | S | 11 | NaN | Montreal, PQ / Chesterville, ON |
| 2 | 1.0 | 0.0 | Allison, Miss. Helen Loraine | female | 2.0000 | 1.0 | 2.0 | 113781 | 151.5500 | C22 C26 | S | NaN | NaN | Montreal, PQ / Chesterville, ON |
| 3 | 1.0 | 0.0 | Allison, Mr. Hudson Joshua Creighton | male | 30.0000 | 1.0 | 2.0 | 113781 | 151.5500 | C22 C26 | S | NaN | 135.0 | Montreal, PQ / Chesterville, ON |
| 4 | 1.0 | 0.0 | Allison, Mrs. Hudson J C (Bessie Waldo Daniels) | female | 25.0000 | 1.0 | 2.0 | 113781 | 151.5500 | C22 C26 | S | NaN | NaN | Montreal, PQ / Chesterville, ON |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1305 | 3.0 | 0.0 | Zabour, Miss. Thamine | female | NaN | 1.0 | 0.0 | 2665 | 14.4542 | NaN | C | NaN | NaN | NaN |
| 1306 | 3.0 | 0.0 | Zakarian, Mr. Mapriededer | male | 26.5000 | 0.0 | 0.0 | 2656 | 7.2250 | NaN | C | NaN | 304.0 | NaN |
| 1307 | 3.0 | 0.0 | Zakarian, Mr. Ortin | male | 27.0000 | 0.0 | 0.0 | 2670 | 7.2250 | NaN | C | NaN | NaN | NaN |
| 1308 | 3.0 | 0.0 | Zimmerman, Mr. Leo | male | 29.0000 | 0.0 | 0.0 | 315082 | 7.8750 | NaN | S | NaN | NaN | NaN |
| 1309 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1310 rows × 14 columns
Zmiana nazw kolumn na zapis w j. polskim¶
## pokaż nazwy wszystkich kolumn
df.columns
Index(['pclass', 'survived', 'name', 'sex', 'age', 'sibsp', 'parch', 'ticket',
'fare', 'cabin', 'embarked', 'boat', 'body', 'home.dest'],
dtype='object')
## zmieniamy nazyw kolumn na zapis w jęzku polskim
df.columns = ['klasa', 'ocalały', 'imię i nazwisko', 'płeć', 'wiek', 'rodzoństwo/małżonek', 'rodzice/dzieci', 'numer biletu',
'cena biletu', 'numer kabiny', 'port', 'numer szalupy', 'numer zwłok', 'miejsce docelowe']
## zmieniamy nazwy płci 'female' i 'male' na j. polski, tak aby wszystko było spójne z polskimi nazwami kolumn
df['płeć'] = df['płeć'].replace({'female': 'kobieta', 'male': 'mężczyzna'})
df
| klasa | ocalały | imię i nazwisko | płeć | wiek | rodzoństwo/małżonek | rodzice/dzieci | numer biletu | cena biletu | numer kabiny | port | numer szalupy | numer zwłok | miejsce docelowe | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1.0 | 1.0 | Allen, Miss. Elisabeth Walton | kobieta | 29.0000 | 0.0 | 0.0 | 24160 | 211.3375 | B5 | S | 2 | NaN | St Louis, MO |
| 1 | 1.0 | 1.0 | Allison, Master. Hudson Trevor | mężczyzna | 0.9167 | 1.0 | 2.0 | 113781 | 151.5500 | C22 C26 | S | 11 | NaN | Montreal, PQ / Chesterville, ON |
| 2 | 1.0 | 0.0 | Allison, Miss. Helen Loraine | kobieta | 2.0000 | 1.0 | 2.0 | 113781 | 151.5500 | C22 C26 | S | NaN | NaN | Montreal, PQ / Chesterville, ON |
| 3 | 1.0 | 0.0 | Allison, Mr. Hudson Joshua Creighton | mężczyzna | 30.0000 | 1.0 | 2.0 | 113781 | 151.5500 | C22 C26 | S | NaN | 135.0 | Montreal, PQ / Chesterville, ON |
| 4 | 1.0 | 0.0 | Allison, Mrs. Hudson J C (Bessie Waldo Daniels) | kobieta | 25.0000 | 1.0 | 2.0 | 113781 | 151.5500 | C22 C26 | S | NaN | NaN | Montreal, PQ / Chesterville, ON |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1305 | 3.0 | 0.0 | Zabour, Miss. Thamine | kobieta | NaN | 1.0 | 0.0 | 2665 | 14.4542 | NaN | C | NaN | NaN | NaN |
| 1306 | 3.0 | 0.0 | Zakarian, Mr. Mapriededer | mężczyzna | 26.5000 | 0.0 | 0.0 | 2656 | 7.2250 | NaN | C | NaN | 304.0 | NaN |
| 1307 | 3.0 | 0.0 | Zakarian, Mr. Ortin | mężczyzna | 27.0000 | 0.0 | 0.0 | 2670 | 7.2250 | NaN | C | NaN | NaN | NaN |
| 1308 | 3.0 | 0.0 | Zimmerman, Mr. Leo | mężczyzna | 29.0000 | 0.0 | 0.0 | 315082 | 7.8750 | NaN | S | NaN | NaN | NaN |
| 1309 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1310 rows × 14 columns
Losowe 10 rekordów¶
df.sample(10)
| klasa | ocalały | imię i nazwisko | płeć | wiek | rodzoństwo/małżonek | rodzice/dzieci | numer biletu | cena biletu | numer kabiny | port | numer szalupy | numer zwłok | miejsce docelowe | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 326 | 2.0 | 0.0 | Andrew, Mr. Edgardo Samuel | mężczyzna | 18.0 | 0.0 | 0.0 | 231945 | 11.5000 | NaN | S | NaN | NaN | Buenos Aires, Argentina / New Jersey, NJ |
| 269 | 1.0 | 0.0 | Smith, Mr. Richard William | mężczyzna | NaN | 0.0 | 0.0 | 113056 | 26.0000 | A19 | S | NaN | NaN | Streatham, Surrey |
| 707 | 3.0 | 0.0 | Carlsson, Mr. August Sigfrid | mężczyzna | 28.0 | 0.0 | 0.0 | 350042 | 7.7958 | NaN | S | NaN | NaN | Dagsas, Sweden Fower, MN |
| 583 | 2.0 | 1.0 | Watt, Mrs. James (Elizabeth "Bessie" Inglis Mi... | kobieta | 40.0 | 0.0 | 0.0 | C.A. 33595 | 15.7500 | NaN | S | 9 | NaN | Aberdeen / Portland, OR |
| 424 | 2.0 | 0.0 | Givard, Mr. Hans Kristensen | mężczyzna | 30.0 | 0.0 | 0.0 | 250646 | 13.0000 | NaN | S | NaN | 305.0 | NaN |
| 944 | 3.0 | 0.0 | Laleff, Mr. Kristo | mężczyzna | NaN | 0.0 | 0.0 | 349217 | 7.8958 | NaN | S | NaN | NaN | NaN |
| 537 | 2.0 | 0.0 | Ponesell, Mr. Martin | mężczyzna | 34.0 | 0.0 | 0.0 | 250647 | 13.0000 | NaN | S | NaN | NaN | Denmark / New York, NY |
| 396 | 2.0 | 1.0 | Doling, Mrs. John T (Ada Julia Bone) | kobieta | 34.0 | 0.0 | 1.0 | 231919 | 23.0000 | NaN | S | NaN | NaN | Southampton |
| 156 | 1.0 | 0.0 | Head, Mr. Christopher | mężczyzna | 42.0 | 0.0 | 0.0 | 113038 | 42.5000 | B11 | S | NaN | NaN | London / Middlesex |
| 1030 | 3.0 | 0.0 | Moran, Mr. James | mężczyzna | NaN | 0.0 | 0.0 | 330877 | 8.4583 | NaN | Q | NaN | NaN | NaN |
Podstawowe informacje o danych zawartych w tabeli¶
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1310 entries, 0 to 1309 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 klasa 1309 non-null float64 1 ocalały 1309 non-null float64 2 imię i nazwisko 1309 non-null object 3 płeć 1309 non-null object 4 wiek 1046 non-null float64 5 rodzoństwo/małżonek 1309 non-null float64 6 rodzice/dzieci 1309 non-null float64 7 numer biletu 1309 non-null object 8 cena biletu 1308 non-null float64 9 numer kabiny 295 non-null object 10 port 1307 non-null object 11 numer szalupy 486 non-null object 12 numer zwłok 121 non-null float64 13 miejsce docelowe 745 non-null object dtypes: float64(7), object(7) memory usage: 143.4+ KB
Wniosek: Tabela posiada 1310 indeksów. Kilka kolumn ma bardzo niepełne dane - do nich należą przede wszystkim kolumny: wiek, numer kabiny, numer szalupy, numer zwłok i miejsce docelowe. Niektóre z kolumn ma zastosowane niewłaściwe typy danych - klasa, wiek, rodzeństwo/małżone, rodzice/dzieci, numer szalupy, numer zwłok - te kolumny powinien być typ INT. Kolumna "ocalały" powinna być stringiem.
Pokazujemy liczbę wartości unikatowych w każdej z kolumn¶
df.nunique()
klasa 3 ocalały 2 imię i nazwisko 1307 płeć 2 wiek 98 rodzoństwo/małżonek 7 rodzice/dzieci 8 numer biletu 929 cena biletu 281 numer kabiny 186 port 3 numer szalupy 27 numer zwłok 121 miejsce docelowe 369 dtype: int64
Wniosek:¶
Kolumna 'imię i nazwisko' nie jest równa ilości wszystkich indeksów, więc prawdopodobnie są 4 rekordy (2 pary duplikatów) z identycznymi imionami i nazwiskami. Z pozostałych kolumn widać, że dane są w dużym stopniu niepełne. Dodatkowo dane zawierają 121 unikalnych wartości dla 'numeru zwłok', co pozwala przypuszczać, że tylko 121 ciał wydobyto z wody po katastrofie, pozostałe ciała spoczęły na dnie oceanu.
Podstawowe obliczenia numeryczne dla poszczególnych kolumn¶
df.describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| klasa | 1309.0 | 2.294882 | 0.837836 | 1.0000 | 2.0000 | 3.0000 | 3.000 | 3.0000 |
| ocalały | 1309.0 | 0.381971 | 0.486055 | 0.0000 | 0.0000 | 0.0000 | 1.000 | 1.0000 |
| wiek | 1046.0 | 29.881135 | 14.413500 | 0.1667 | 21.0000 | 28.0000 | 39.000 | 80.0000 |
| rodzoństwo/małżonek | 1309.0 | 0.498854 | 1.041658 | 0.0000 | 0.0000 | 0.0000 | 1.000 | 8.0000 |
| rodzice/dzieci | 1309.0 | 0.385027 | 0.865560 | 0.0000 | 0.0000 | 0.0000 | 0.000 | 9.0000 |
| cena biletu | 1308.0 | 33.295479 | 51.758668 | 0.0000 | 7.8958 | 14.4542 | 31.275 | 512.3292 |
| numer zwłok | 121.0 | 160.809917 | 97.696922 | 1.0000 | 72.0000 | 155.0000 | 256.000 | 328.0000 |
Wniosek: z powyższych danych numerycznych można wyczytać kilka ciekawych rzeczy:¶
- średni wiek pasażerów to niespełna 30 lat, a najczęściej powtarzającym sie wiekiem jest wiek 28 lat, najmłodszym pasażerem było 2-miesięczne dziecko, a najstarszy pasażer miał 80 lat
- większość pasażerów podróżowała sama, bez rodziny
- średnia cena biletu wynosiła 33 jednostki (brak informacji w jakiej walucie podano cenę biletu prawdopodobnie jest to funt brytyjski lub dolar amerykański). Jednakże zróżnicowanie cen jest duże, w zależności od klasy, widać rozrzut danych o 0 do 512.
2. Analiza wartości brakujących¶
Sprawdzanie ile rekordów ma puste wartości w każdej z kolumn¶
df.isnull().sum()
klasa 1 ocalały 1 imię i nazwisko 1 płeć 1 wiek 264 rodzoństwo/małżonek 1 rodzice/dzieci 1 numer biletu 1 cena biletu 2 numer kabiny 1015 port 3 numer szalupy 824 numer zwłok 1189 miejsce docelowe 565 dtype: int64
Czy któryś z rekordów ma puste wszystkie kolumny?¶
df.isnull().all(axis=1).sum()
1
Usuwanie rekordu, który ma puste wartości we wszystkich kolumnach¶
df = df.dropna(how='all')
## Parametr 'how' jest ustawiony na 'all', co oznacza, że zostaną usunięte tylko te wiersze, w których wszystkie wartości są brakujące.
## Upewniamy się, że teraz nie ma już rekordu który ma wszystkie kolumny puste
df.isnull().all(axis=1).sum()
0
Zduplikowane rekordy¶
df[df.duplicated()]
| klasa | ocalały | imię i nazwisko | płeć | wiek | rodzoństwo/małżonek | rodzice/dzieci | numer biletu | cena biletu | numer kabiny | port | numer szalupy | numer zwłok | miejsce docelowe |
|---|
df[df['imię i nazwisko'].duplicated()]
| klasa | ocalały | imię i nazwisko | płeć | wiek | rodzoństwo/małżonek | rodzice/dzieci | numer biletu | cena biletu | numer kabiny | port | numer szalupy | numer zwłok | miejsce docelowe | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 726 | 3.0 | 0.0 | Connolly, Miss. Kate | kobieta | 30.0 | 0.0 | 0.0 | 330972 | 7.6292 | NaN | Q | NaN | NaN | Ireland |
| 925 | 3.0 | 0.0 | Kelly, Mr. James | mężczyzna | 44.0 | 0.0 | 0.0 | 363592 | 8.0500 | NaN | S | NaN | NaN | NaN |
## Pokaż rekordy z zduplikowanymi wartościami w kolumnie 'imię i nazwisko', aby móc porównać pozostałe dane dla tych rekordów
duplicate_names = df[df.duplicated(subset='imię i nazwisko', keep=False)]
duplicate_names
| klasa | ocalały | imię i nazwisko | płeć | wiek | rodzoństwo/małżonek | rodzice/dzieci | numer biletu | cena biletu | numer kabiny | port | numer szalupy | numer zwłok | miejsce docelowe | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 725 | 3.0 | 1.0 | Connolly, Miss. Kate | kobieta | 22.0 | 0.0 | 0.0 | 370373 | 7.7500 | NaN | Q | 13 | NaN | Ireland |
| 726 | 3.0 | 0.0 | Connolly, Miss. Kate | kobieta | 30.0 | 0.0 | 0.0 | 330972 | 7.6292 | NaN | Q | NaN | NaN | Ireland |
| 924 | 3.0 | 0.0 | Kelly, Mr. James | mężczyzna | 34.5 | 0.0 | 0.0 | 330911 | 7.8292 | NaN | Q | NaN | 70.0 | NaN |
| 925 | 3.0 | 0.0 | Kelly, Mr. James | mężczyzna | 44.0 | 0.0 | 0.0 | 363592 | 8.0500 | NaN | S | NaN | NaN | NaN |
Wniosek: Dane nie mają zduplikowanych rekordów. Sprawdzono również zduplikowane dane w kolumnie 'imię i nazwisko' i wykryto 2 pary rekordów z takimi samymi imionami i nazwiskami, jednakże jest to prawdopodobne zdarzenie, pozostałe dane dla tych rekordów różnią się od siebie.
Zmiana typu danych na string dla kolumny 'Ocalały' i przypisanie dla ocalałych wartości "Tak" a dla zabitych wartości "Nie"¶
## Zmiana typu na string
df['ocalały'] = df['ocalały'].replace({1.0: 'Tak', 0.0: 'Nie'}).astype(str)
df['ocalały'].info()
<class 'pandas.core.series.Series'> Int64Index: 1309 entries, 0 to 1308 Series name: ocalały Non-Null Count Dtype -------------- ----- 1309 non-null object dtypes: object(1) memory usage: 20.5+ KB
C:\Users\Łukasz\AppData\Local\Temp\ipykernel_2352\3388569788.py:2: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df['ocalały'] = df['ocalały'].replace({1.0: 'Tak', 0.0: 'Nie'}).astype(str)
3. Transformacja danych¶
Uzupełnianie pustych wartości w kolumnie 'wiek' medianą wyliczoną osobno dla mężczyzn i kobiet oraz dodatkowo z podziałem na klasy.¶
## sprawdzanie ile jest pustych wartości w kolumnie 'wiek'
df['wiek'].isna().sum()
263
## obliczamy medianę wieku dla mężczyzn i kobiet dodatkowo grupując to jeszcze na klasy
mezczyzni = df[df['płeć'] == 'mężczyzna']
mezczyzni_mediana_wiek = mezczyzni.groupby('klasa')['wiek'].median()
kobiety = df[df['płeć'] == 'kobieta']
kobiety_mediana_wiek = kobiety.groupby('klasa')['wiek'].median()
## wyświetlamy wynik obliczeń w tabeli, nie jest to krok konieczny, chodzi tylko o przedstawienie wizualne jak prezentuje sie wiek w poszczególnych klasach w zależności od płci
df_mediana_wieku = pd.DataFrame({
'mediana wieku mężczyźni': mezczyzni_mediana_wiek,
'mediana wieku kobiety': kobiety_mediana_wiek
})
df_mediana_wieku
| mediana wieku mężczyźni | mediana wieku kobiety | |
|---|---|---|
| klasa | ||
| 1.0 | 42.0 | 36.0 |
| 2.0 | 29.5 | 28.0 |
| 3.0 | 25.0 | 22.0 |
Uzupełnie pustych pól w kolumnie wiek medianą wieku obliczoną osobno dla kobiet i mężczyzn i grupująć ich dodatkowo według klasy¶
## funkcja do uzupełniania pustych pól w kolumnie wiek
def uzupelnij_wiek(rzad):
if pd.isna(rzad['wiek']) and rzad['płeć'] == 'mężczyzna':
return mezczyzni_mediana_wiek.get(rzad['klasa'], rzad['wiek'])
elif pd.isna(rzad['wiek']) and rzad['płeć'] == 'kobieta':
return kobiety_mediana_wiek.get(rzad['klasa'], rzad['wiek'])
return rzad['wiek']
df['wiek'] = df.apply(uzupelnij_wiek, axis=1)
C:\Users\Łukasz\AppData\Local\Temp\ipykernel_2352\4072451876.py:9: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df['wiek'] = df.apply(uzupelnij_wiek, axis=1)
## sprawdzenie czy powyższa funkcja 'uzupelnij wiek' działą prawidłowo - wyświetlamy indeks 15 tabeli (wiersz 16), w którym był mężczyzna z 1 klasy a który nie miał przypisanego wieku
df.iloc[15]
klasa 1.0 ocalały Nie imię i nazwisko Baumann, Mr. John D płeć mężczyzna wiek 42.0 rodzoństwo/małżonek 0.0 rodzice/dzieci 0.0 numer biletu PC 17318 cena biletu 25.925 numer kabiny NaN port S numer szalupy NaN numer zwłok NaN miejsce docelowe New York, NY Name: 15, dtype: object
4. Analiza pojedyńczych zmiennych¶
Podział pasażerów na dzieci, kobiety i mężczyzn
import matplotlib.pyplot as plt
# Calculate the number of children, women, and men
ilosc_dzieci = df[df['wiek'] < 18].shape[0]
ilosc_kobiet = df[(df['płeć'] == 'kobieta') & (df['wiek'] >= 18)].shape[0]
ilosc_mezczyzn = df[(df['płeć'] == 'mężczyzna') & (df['wiek'] >= 18)].shape[0]
# Total number of passengers
suma_pasazerow = df.shape[0]
# Calculate percentages
dzieci_procent = (ilosc_dzieci / suma_pasazerow) * 100
kobiety_procent = (ilosc_kobiet / suma_pasazerow) * 100
mezczyzni_procent = (ilosc_mezczyzn / suma_pasazerow) * 100
# Data for the pie chart
labels = ['Dzieci', 'Kobiety', 'Mężczyźni']
sizes = [dzieci_procent, kobiety_procent, mezczyzni_procent]
colors = ['#ff9999','#66b3ff','#99ff99']
explode = (0.1, 0.1, 0.1) # explode all slices
# Create the pie chart
plt.figure(figsize=(8, 8))
plt.pie(sizes, explode=explode, labels=labels, colors=colors, autopct='%1.1f%%', startangle=140, wedgeprops={'edgecolor': 'black'})
plt.title('Rozkład procentowy pasażerów - dzieci, kobiety, mężczyźni')
# Equal aspect ratio ensures that pie is drawn as a circle.
plt.axis('equal')
(-1.2034621067247384, 1.1903676209341627, -1.1904827458015403, 1.1649285137017713)
Ile osób samotnych (bez rodziny i małżonka)¶
# Filter passengers who have neither siblings/spouses nor parents/children on board
osoby_samotne = df[(df['rodzoństwo/małżonek'] == 0) & (df['rodzice/dzieci'] == 0)]
# Count the number of such passengers
suma_samotnych = osoby_samotne.shape[0]
suma_samotnych
790
osoby_samotne_klasa = osoby_samotne.groupby(['klasa'])
osoby_samotne_klasa
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000002521E6273D0>
suma_ocalałych = (df['ocalały'] == 'Tak').sum()
suma_ocalałych
500
suma_zabitych = (df['ocalały'] == 'Nie').sum()
suma_zabitych
809
import matplotlib.pyplot as plt
# Create a bar chart
plt.figure(figsize=(8, 6))
plt.bar(['Ocalali', 'Zabici'], [suma_ocalałych, suma_zabitych], color=['green', 'red'])
plt.title('Liczba ocalałaych i zabitych')
plt.xlabel('Status')
plt.ylabel('Liczba osób')
Text(0, 0.5, 'Liczba osób')
df['wiek'].hist(bins=30, edgecolor='black')
plt.title('Histogram wieku pasażerów')
plt.xlabel('Wiek')
plt.ylabel('Liczba pasażerów')
Text(0, 0.5, 'Liczba pasażerów')
Wniosek: na statku przeważali pasażerowie młodzi, najczęściej między 20 a 30 rokiem życia. Mediana wieku wynosiła 28 lat. Najmniejszą liczbę stanowiły osoby w wieku powyżej 65 lat oraz dzieci między 8-12 rokiem życia.
## sprawdzamy ile razy występuje puste pole w cenie biletu lub wartość 0 - tych wartości nie chcemy uwzględniać w histogramie
zerowa_cena_biletu = df['cena biletu'].value_counts().get(0, 0)
zerowa_cena_biletu
17
filtr_cena_biletu = df['cena biletu'].dropna()
filtr_cena_biletu = filtr_cena_biletu[(filtr_cena_biletu > 0) & (filtr_cena_biletu < 300)]
plt.hist(filtr_cena_biletu, bins=50, edgecolor='black')
plt.title('Histogram ceny biletu')
plt.xlabel('Cena')
plt.ylabel('Liczba wystąpień')
Text(0, 0.5, 'Liczba wystąpień')
## Tworzymy tabelę df2, jako kopię df, w której zapiszemy tylko kolumny 'klasa' i 'ocalały', po to aby w nastepnym kroku zmienić nazwy klas na string, bo na wykresie lepiej wygląda zapis "klasa 1" niż "1.0"
df2 = df[['klasa', 'ocalały']].copy()
df2['klasa'] = df2['klasa'].map({1.0: 'klasa 1', 2.0: 'klasa 2', 3.0: 'klasa 3'})
ocalały_klasa = df2.groupby(['klasa', 'ocalały']).size().unstack()
ocalały_procentowo = ocalały_klasa.div(ocalały_klasa.sum(axis=1), axis=0) * 100
fig, ax = plt.subplots(figsize=(8, 6))
ocalały_klasa.plot(kind='bar', stacked=True, ax=ax, color=['red', 'green'])
for i, (klasa, rzad) in enumerate(ocalały_procentowo.iterrows()):
for j, (ocalały, procenty) in enumerate(rzad.items()):
ax.text(i, ocalały_klasa.iloc[i, j] / 2 + sum(ocalały_klasa.iloc[i, :j]), f'{procenty:.1f}%', ha='center', va='center', color='white')
ax.set_title('Ocaleni z podziałem na klasy')
ax.set_xlabel('Klasa')
ax.set_ylabel('Liczba osób')
ax.legend(['Zabici', 'Ocaleni'], title='Legenda')
<matplotlib.legend.Legend at 0x2520e782b90>
ocalały_klasa
| ocalały | Nie | Tak |
|---|---|---|
| klasa | ||
| klasa 1 | 123 | 200 |
| klasa 2 | 158 | 119 |
| klasa 3 | 528 | 181 |
ocaleni_płeć = df.groupby(['płeć', 'ocalały'])
ocaleni_płeć
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000002520E9B1B10>
ocalały_płeć = df.groupby(['płeć', 'ocalały']).size().unstack()
ocalały_procentowo = ocalały_płeć.div(ocalały_płeć.sum(axis=1), axis=0) * 100
ax = ocalały_procentowo.plot(kind='bar', stacked=True, figsize=(8, 6), color=['red', 'green'])
ax.set_title('Ocaleni z podziałem na płeć')
ax.set_xlabel('Płeć')
ax.set_ylabel('Ile procent?')
ax.legend(['Zabici', 'Ocaleni'], title='Legenda')
for p in ax.patches:
width, height = p.get_width(), p.get_height()
x, y = p.get_xy()
ax.annotate(f'{height:.1f}%', (x + width/2, y + height/2), ha='center', va='center')
ocalały_płeć
| ocalały | Nie | Tak |
|---|---|---|
| płeć | ||
| kobieta | 127 | 339 |
| mężczyzna | 682 | 161 |
plt.hist(df[df['ocalały'] == 'Nie']['wiek'], bins=30, alpha=0.5, label='Zabici', color='r')
# Histogram for not survived passengers
## plt.hist(df[df['ocalały'] == 'Tak']['wiek'], bins=10, alpha=0.5, label='Not Survived', color='r')
# Add labels and title
plt.xlabel('Wiek')
plt.ylabel('Liczba osób')
plt.title('Histogram dla osób zabitych z podziałem na wiek')
plt.legend()
<matplotlib.legend.Legend at 0x2520c34e310>
# Histogram for not survived passengers
plt.hist(df[df['ocalały'] == 'Tak']['wiek'], bins=30, alpha=0.5, label='Ocaleni', color='g')
# Add labels and title
plt.xlabel('Wiek')
plt.ylabel('Liczba osób')
plt.title('Histogram dla osób ocalonych z podziałem na wiek')
plt.legend()
<matplotlib.legend.Legend at 0x2520e7cc350>
Wniosek: histogramy wieku zabitych i ocalałych są zbliżone do ogólnego histogramu wieku wszystkich pasażerów.
Porównanie skali zabitych z podziałem na dzieci i dorosłych¶
# Create two subsets based on age
dzieci = df[df['wiek'] < 18]
dorośli = df[df['wiek'] >= 18]
# Calculate the counts for each group
dzieci_ocalałe = dzieci['ocalały'].value_counts()
dorośli_ocaleni = dorośli['ocalały'].value_counts()
# Plot the pie chart for under 18
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.pie(dzieci_ocalałe, labels=['Zabici', 'Ocalali'], autopct='%1.1f%%', startangle=90)
plt.title('Dzieci - wiek poniżej 18 lat')
# Plot the pie chart for 18 and over
plt.subplot(1, 2, 2)
plt.pie(dorośli_ocaleni, labels=['Zabici', 'Ocalali'], autopct='%1.1f%%', startangle=90)
plt.title('Dorośli - wiek 18 lat i więcej')
Text(0.5, 1.0, 'Dorośli - wiek 18 lat i więcej')
Wsniosek: Odsetek ocalałych wśród dzieci jest nieco większy niż wśród dorosłych (o około 11 pkt %), ale wciąż więcej było zabitych (52,6%). Jest to dość zaskakująca dana, ponieważ nie znając wyniku tej analizy możnaby sądzić że dzieci były ratowane w dużo większym odsetku.
ilosc_dzieci
154
# Filter children below 18 years of age
dzieci_df = df[df['wiek'] < 18]
# Group by class and count the number of children in each class
dzieci_klasy = dzieci_df['klasa'].value_counts().sort_index()
# Plot a pie chart
plt.figure(figsize=(8, 8))
plt.pie(dzieci_klasy, labels=dzieci_klasy.index, autopct='%1.1f%%', startangle=90, wedgeprops={'edgecolor': 'black'})
plt.title('Do jakich klas należały dzieci?')
plt.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle.
(-1.09998871840244, 1.0999993357476645, -1.0999992641338077, 1.0999999649587529)
# Import the required dependencies
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# Filter children (age < 18)
dzieci_df = df[df['wiek'] < 18]
# Group by class and survival status
grouped = dzieci_df.groupby(['klasa', 'ocalały']).size().unstack(fill_value=0)
# Calculate percentages of survivors
grouped['total'] = grouped['Nie'] + grouped['Tak']
grouped['survived_percent'] = (grouped['Tak'] / grouped['total']) * 100
# Plotting
fig, ax = plt.subplots(figsize=(10, 6))
# Bar plot for survived and not survived
bar_width = 0.35
index = np.arange(len(grouped))
bars1 = ax.bar(index, grouped['Nie'], bar_width, label='Zabity', color='red')
bars2 = ax.bar(index + bar_width, grouped['Tak'], bar_width, label='Ocalały', color='green')
# Add percentage labels on green bars
for i, rect in enumerate(bars2):
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width() / 2.0, height, f'{grouped["survived_percent"].iloc[i]:.1f}%', ha='center', va='bottom', color='gray')
# Labels and title
ax.set_xlabel('Klasa')
ax.set_ylabel('Liczba dzieci')
ax.set_title('Ocalałe dzieci z podziałem na klasy')
ax.set_xticks(index + bar_width / 2)
ax.set_xticklabels(['1st Class', '2nd Class', '3rd Class'])
ax.legend()
plt.tight_layout()
plt.figure(figsize=(8, 6))
df['port'].dropna().value_counts().plot(kind='bar', color='skyblue')
plt.title('W którym porcie wsiadło na pokład najwięcej osób')
plt.xlabel('Port (C = Cherbourg, Q = Queenstown, S = Southampton)')
plt.ylabel('Liczba pasażerów')
plt.xticks(rotation=0)
plt.tight_layout()
Wniosek: Najwięcej osób weszło na statek w porcie w Southampton (blisko 900), około 300 osób w Cherbourg, a około 100 osób w Queenstown.
# Group by 'home.dest' and count the number of occurrences
destination_counts = df['miejsce docelowe'].value_counts().head(10)
# Plotting
plt.figure(figsize=(10, 6))
destination_counts.plot(kind='bar', color='skyblue')
plt.title('Top 10 destynacji')
plt.xlabel('Destynacja')
plt.ylabel('Liczba osób')
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
import matplotlib.pyplot as plt
import seaborn as sns
# Calculate the correlation matrix
correlation_matrix = df.corr()
# Plot the correlation matrix
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, fmt=".2f", cmap='coolwarm', cbar=True)
plt.title('Macierz korelacji')
plt.tight_layout()
C:\Users\Łukasz\AppData\Local\Temp\ipykernel_2352\2928059081.py:5: FutureWarning: The default value of numeric_only in DataFrame.corr is deprecated. In a future version, it will default to False. Select only valid columns or specify the value of numeric_only to silence this warning. correlation_matrix = df.corr()
Wniosek: w analizowanych danych nie ma jakiś bardzo dużych zależności między danymi. Widać tylko pewną zależność ceny biletu od klasy - im wyższa klasa tym droższy bilet, oraz wieku od klasy - im wyższa klasa tym starsi pasażerowie
5. Wartości odstające¶
plt.figure(figsize=(12, 6))
# Boxplot for 'age'
plt.subplot(1, 2, 1)
df.boxplot(column='wiek')
plt.title('Boxplot dla wieku')
# Boxplot for 'fare'
plt.subplot(1, 2, 2)
df.boxplot(column='cena biletu')
plt.title('Boxplot dla ceny biletu')
Text(0.5, 1.0, 'Boxplot dla ceny biletu')
6. Podsumowanie i wnioski¶
Przegląd danych oraz dokonane zmiany i transformacje danych.¶
- Tabela zawiera 1310 indeksów, każdy rekord odpowiada 1 osobie płynącej statkiem Titanic.
- Według ogólnodostępnych informacji statkiem Titanic płynęło około 2200 osób, łącznie z załogą, a więc dane które zawiera analizowana tabela nie obejmują wszysztich osób na pokładzie, jest to około 60% wszystkich osób płynących statkiem. To też może powodować przekłamanie i nieco fałszować realne statystyki z katastrofy Titanica. Jednakże analiza została przeprowadzona szczegółowo na danych które posiadamy.
- Zmieniono nazwy kolumn na zapis w j. polskim.
- Kilka kolumn ma znacząco niepełne dane - do nich należą przede wszystkim kolumny: wiek, numer kabiny, numer szalupy, numer zwłok i miejsce docelowe. Tylko 1 rekord był całkowicie pusty i nie posiadał wartości w żadnej z kolumn - rekord ten został usunięty.
- Wśród niepełnych danych ciekawym przypadkiem może być tylko 121 rekordów, które posiadały wpisany numer zwłok. Ogółem liczba zabitych z analizowanej tabeli wynosi 809 osób. Tylko `121 rekordów z przypisanym numerem zwłok pozwala przypuszczać, że tylko 121 ciał wydobyto z wody po katastrofie.
- Dane zawierały tylko 2 pary duplikatów w kolumnie dotyczącej imienia i nazwiska pasażera. Nie usuwano tych duplikatów bo powtórzenie się popularnego imienia i nazwiska dla tak dużej liczby osób jest zdarzeniem prawdopodobnym, a dodatkowo dane w innych kolumnach różniły się od siebie.
- Zmieniono typ danych dla kolumny "ocalały" z float na string. Kolumna 'ocalały' mówi o tym czy osoba przeżyła czy zginęła, domyślnie w tabeli było to zapisane w sposób zero-jedynkowy, gdzie 0 oznaczało że osoba zginęła a 1, że osoba przeżyła. Dla lepszej przejrzystości i łatwiejszej analizy lepiej jest tą wartość odczytywać słownie a nie w sposób binarny.
- Puste wartości w kolumnie wiek uzupełniono medianą wyliczoną osobno dla mężczyzn i kobiet oraz dodatkowo z podziałem na klasy.
Wnioski do analizy zmiennych i wartości odstających¶
- Na statku przeważali dorośli mężczyźni - 58% pasażerów, kobiet było 30%, a dzieci 12%.
- Większość pasażerów była samotna, czyli płynęła bez rodziny czy małżonka (790 osób co stanowi 60% wszystkich).
- Na statku przeważali pasażerowie młodzi, najczęściej między 20 a 30 rokiem życia. Mediana wieku wynosiła 28 lat, a średnia niespełna 30 lat. Najmniejszą liczbę stanowiły osoby w wieku powyżej 65 lat oraz dzieci między 8-12 rokiem życia. Najmłodszym pasażerem było 2-miesięczne dziecko, a najstarszy pasażer miał 80 lat.
- Średnia cena biletu wynosiła 33 jednostki (brak informacji w jakiej walucie podano cenę biletu prawdopodobnie jest to funt brytyjski lub dolar amerykański). Jednakże zróżnicowanie cen jest duże, w zależności od klasy, widać rozrzut danych od 0 do 512.
- Ogólna liczba zabitych to 809 spośród 1310 osób, co stanowi około 62%.
- stosunek zabitych do ocalałych rośnie wraz ze stopniem klasy do której należeli pasażerowie - im gorsza klasa tym większy procent zabitych - np. w klasie 1 "tylko" 38% osób zginęło, podczas gdy w klasie 2 było to już 57%, a w klasie 3 aż 75%
- zdecydowanie większy odsetek ofiar stanowili mężczyźni - na 809 ofiar 127 to były kobiety a 682 mężczyźni, co daje odpowiednio 15,7% ofiar o płci żeńskiej i 84,3% płci męskiej. Jednakże wiemy, że na statku zdecydowanie przeważali mężczyźni, dlatego sprawdzono wskaźnik ofiar również osobno dla kobiet i mężczyzn - kobiet zginęło 27% a mężczyzn aż 81%. To oznacza, że tylko co piąty mężczyzna przeżył.
- 53% dzieci nie przeżyło, 47% ocalało. To dość zaskakujące, ponieważ nie znając wyniku tej analizy można by sądzić, że dzieci były ratowane w dużo większym odsetku - a jednak nie. Przeżywalność katastrofy wśród dzieci jest zbliżona do dorosłych, różni się tylko o ok. 11 pkt.% na korzyść dzieci. Jednakże tak duży odsetek ofiar wśród dzieci może tłumaczyć analiza następnych danych, która wykazuje że zdecydowana większość dzieci należała do klasy 3, w której jak już wcześniej pokazano, odsetek ofiar był dużo wyższy niż w klasie 1 i 2
- na statku było 154 dzieci, prawie 70% należało do 3 klasy, 20% do 2 klasy, a tylko 10% do klasy 1
- różnica w ofiarach wśród dzieci z podziałem na klasy jest porażająca - w klasie 3 tylko 37% dzieci przeżyło, podczas gdy w klasie 1 i 2 było to odpowiednio 87% i 88%. W liczbach bezwzględnych różnicę widać jeszcze bardziej: łącznie w klasie 1 i 2 zginęło tylko 6 dzieci, natomiast w klasie 3 aż 67 dzieci
- Najwięcej osób weszło na statek w porcie w Southampton (blisko 900), około 300 osób w Cherbourg, a około 100 osób w Queenstown.
- W analizowanych danych nie ma jakiś bardzo dużych zależności między danymi. Widać tylko pewną zależność ceny biletu od klasy - im wyższa klasa tym droższy bilet, oraz wieku od klasy - im wyższa klasa tym starsi pasażerowie
- Połowa pasażerów była między 22 a 36 rokiem życia. Wartościami odstającymi dla wieku były wartości poniżej 1 roku życia i powyżej 60 roku życia. Natomiast dla cen biletu wartościami odstającymi były wartości powyżej 70, a większość cen biletów mieściłą się w przedziale 10 do 30.