Analiza Danych EDA Titanica: Eksploracja Domenowa
Data wykonania projektu: luty/marzec 2025
Analiza danych osób, które przeżyły bądź nie przeżyły katastrofy statku Titanic dnia 12.04.1912 roku. Jest to czysta analiza danych wywnioskowana z tabeli ofiar tragedii. Na różnych wykres i tablicach można ocenić różne aspekty przetrwania, np. kto i z jakiej klasy pasażerskiej miał większą szansę przeżyć tragedię. Zachęcam do zapoznania się z treścią notebooka, który można sobie pobrać poniżej.
Pobierz Notebook Pobierz Plik Bazy Danych
Analiza TITANIC¶
Import bibliotek¶
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
Import pliku z bazą danych i pokazanie 10 losowych wierszy¶
df = pd.read_csv('26__titanic.csv', sep=',')
df.sample(10)
| pclass | survived | name | sex | age | sibsp | parch | ticket | fare | cabin | embarked | boat | body | home.dest | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 480 | 2.0 | 0.0 | Laroche, Mr. Joseph Philippe Lemercier | male | 25.0 | 1.0 | 2.0 | SC/Paris 2123 | 41.5792 | NaN | C | NaN | NaN | Paris / Haiti |
| 943 | 3.0 | 0.0 | Laitinen, Miss. Kristina Sofia | female | 37.0 | 0.0 | 0.0 | 4135 | 9.5875 | NaN | S | NaN | NaN | NaN |
| 543 | 2.0 | 0.0 | Reeves, Mr. David | male | 36.0 | 0.0 | 0.0 | C.A. 17248 | 10.5000 | NaN | S | NaN | NaN | Brighton, Sussex |
| 189 | 1.0 | 0.0 | Long, Mr. Milton Clyde | male | 29.0 | 0.0 | 0.0 | 113501 | 30.0000 | D6 | S | NaN | 126.0 | Springfield, MA |
| 44 | 1.0 | 1.0 | Burns, Miss. Elizabeth Margaret | female | 41.0 | 0.0 | 0.0 | 16966 | 134.5000 | E40 | C | 3 | NaN | NaN |
| 610 | 3.0 | 0.0 | Ahlin, Mrs. Johan (Johanna Persdotter Larsson) | female | 40.0 | 1.0 | 0.0 | 7546 | 9.4750 | NaN | S | NaN | NaN | Sweden Akeley, MN |
| 526 | 2.0 | 1.0 | Pallas y Castello, Mr. Emilio | male | 29.0 | 0.0 | 0.0 | SC/PARIS 2147 | 13.8583 | NaN | C | 9 | NaN | Spain / Havana, Cuba |
| 698 | 3.0 | 0.0 | Cacic, Mr. Jego Grga | male | 18.0 | 0.0 | 0.0 | 315091 | 8.6625 | NaN | S | NaN | NaN | NaN |
| 1161 | 3.0 | 0.0 | Rush, Mr. Alfred George John | male | 16.0 | 0.0 | 0.0 | A/4. 20589 | 8.0500 | NaN | S | NaN | NaN | NaN |
| 1259 | 3.0 | 0.0 | Turcin, Mr. Stjepan | male | 36.0 | 0.0 | 0.0 | 349247 | 7.8958 | NaN | S | NaN | NaN | NaN |
Prośba o podanie parametrów bazy (informacji o polach)¶
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1310 entries, 0 to 1309 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 pclass 1309 non-null float64 1 survived 1309 non-null float64 2 name 1309 non-null object 3 sex 1309 non-null object 4 age 1046 non-null float64 5 sibsp 1309 non-null float64 6 parch 1309 non-null float64 7 ticket 1309 non-null object 8 fare 1308 non-null float64 9 cabin 295 non-null object 10 embarked 1307 non-null object 11 boat 486 non-null object 12 body 121 non-null float64 13 home.dest 745 non-null object dtypes: float64(7), object(7) memory usage: 143.4+ KB
W wielu rekordach brakuje danych, np. w informacji o przypisaniu do kabiny lub czy ciało zostało odnalezione.
Prośba o wyświetlenie duplikatów¶
df[df.duplicated()]
| pclass | survived | name | sex | age | sibsp | parch | ticket | fare | cabin | embarked | boat | body | home.dest |
|---|
Duplikatów brak
Prośba o wyświetlenie unikatowych wartości¶
df.nunique()
pclass 3 survived 2 name 1307 sex 2 age 98 sibsp 7 parch 8 ticket 929 fare 281 cabin 186 embarked 3 boat 27 body 121 home.dest 369 dtype: int64
Tu można zauważyć, które pola są polami typu bool, można to porównać z informacją o bazie danych i potwierdzić. Np. kolumna 'survived' przyjmuje tylko
dwie wartości unikatowe - survived(1) lub not survived(0). Czyli mimo iż jest to pole typu Float, w rzeczywistości jest polem logicznym True or False.
Mimo wszystko pozostawię to pole takim jakie ono jest i w toku dalszych przeliczeń sprawdzę czy do wykresów lepiej się nadaje jako pole logiczne czy Float.
Prośba o wyświetlenie wykresu słupkowego według tego kto przeżył z podziałem na płeć¶
df_clean = df.dropna(subset=['survived', 'sex'])
survived_sex_counts = df_clean.groupby(['sex', 'survived']).size().unstack()
survived_sex_counts.plot(kind='bar', stacked=True)
plt.title('Przeżywalność według płci')
plt.xlabel('Płeć')
plt.ylabel('Liczba')
plt.xticks(rotation=0)
plt.legend(title='Przeżyli', labels=['Nie', 'Tak'])
Na wykresie wyraźnie widać, że stosunkowo więcej kobiet niż mężczyzn uratowało się
Czyli można wyciągnąć wniosek, że brano pod uwagę to czy osoba ratowana była kobietą a także, że w takim wypadku należał jej się priorytet miejsca w szalupie.
Prośba o pokazanie liczby pasażerów wg. podziału na płeć¶
pwp = df['sex'].value_counts()
result = {
"type": "dataframe",
"value": pwp.to_frame(name='count')
}
pwp
sex male 843 female 466 Name: count, dtype: int64
Tutaj wyraźnie widać, że mężczyzn na pokładzie było więcej niemal dwukrotnie od kobiet, co wzmacnia poziom relacji z wykresu powyżej, ponieważ mężczyzn zginęło więcej.
Wniosek:¶
mężczyzna miał o wiele mniejsze szanse przetrwania niż kobieta
Prośba o pokazanie wykresu punktowego uczestników rejsu według podziału na wiek¶
plt.figure(figsize=(10, 6))
plt.scatter(df['age'], df.index, alpha=0.5)
plt.title('Uczestnicy rejsu w podziale na wiek')
plt.xlabel('Wiek')
plt.ylabel('Index')
plt.grid(True)
Wykres punktowy przedstawia zakres wiekowy podróżnych Wniosek jaki się nasuwa to to, że większośc pasażerów była w wieku 20-30 lat
A teraz pokażę ten sam wykres z oznaczeniem innym kolorem wszystkich pasażerów poniżej 18 roku życia.¶
df = df.dropna(subset=['age'])
#Oddzielenie dorosłych od dzieci
children = df[df['age'] < 18]
adults = df[df['age'] >= 18]
# Zliczenie dorosłych i dzieci
num_children = len(children)
num_adults = len(adults)
# Stworzenie wykresu
plt.figure(figsize=(10, 6))
plt.scatter(children['age'], children.index, color='blue', label=f'Dzieci (<18): {num_children}')
plt.scatter(adults['age'], adults.index, color='red', label=f'Dorośli (>=18): {num_adults}')
# Dodanie nazw osi i legendy
plt.xlabel('Wiek')
plt.ylabel('Passenger Index')
plt.title('Wykres punktowy uczestników według podziału na płeć')
plt.legend()
# Reverse axes
plt.gca().invert_yaxis()
W kolumnie age w samej bazie danych mamy tylko 1046 elementów i ten wykres jest zrobiony tylko dla tej grupy, ale wyraźnie widać, na podstawie tak dużej grupy próbnej jak kształtował się podział wiekowy i jaki mniej więcej odsetek stanowiły dzieci.
Procentowo wyglądało to tak:
# Przeliczenie dorosłych i dzieci
children_count = df[df['age'] < 18].shape[0]
adults_count = df[df['age'] >= 18].shape[0]
# Stworzenie wykresu kołowego
labels = ['Dzieci (<18)', 'Dorośli (18+)']
sizes = [children_count, adults_count]
colors = ['#66b3ff','#ff9999']
explode = (0.1, 0) # wykrojenie kawałka
plt.figure(figsize=(8, 8))
plt.pie(sizes, explode=explode, labels=labels, colors=colors, autopct='%1.1f%%', shadow=True, startangle=140)
plt.axis('equal')
A jak nazywały się najmłodsze osoby które przeżyły katastrofę?¶
titanic_df = df
prz_df = titanic_df[titanic_df['survived'] == 1]
najm = prz_df.nsmallest(10, 'age')
result = {
"type": "dataframe",
"value": najm
}
najm
| pclass | survived | name | sex | age | sibsp | parch | ticket | fare | cabin | embarked | boat | body | home.dest | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 763 | 3.0 | 1.0 | Dean, Miss. Elizabeth Gladys "Millvina" | female | 0.1667 | 1.0 | 2.0 | C.A. 2315 | 20.5750 | NaN | S | 10 | NaN | Devon, England Wichita, KS |
| 1240 | 3.0 | 1.0 | Thomas, Master. Assad Alexander | male | 0.4167 | 0.0 | 1.0 | 2625 | 8.5167 | NaN | C | 16 | NaN | NaN |
| 427 | 2.0 | 1.0 | Hamalainen, Master. Viljo | male | 0.6667 | 1.0 | 1.0 | 250649 | 14.5000 | NaN | S | 4 | NaN | Detroit, MI |
| 657 | 3.0 | 1.0 | Baclini, Miss. Eugenie | female | 0.7500 | 2.0 | 1.0 | 2666 | 19.2583 | NaN | C | C | NaN | Syria New York, NY |
| 658 | 3.0 | 1.0 | Baclini, Miss. Helene Barbara | female | 0.7500 | 2.0 | 1.0 | 2666 | 19.2583 | NaN | C | C | NaN | Syria New York, NY |
| 359 | 2.0 | 1.0 | Caldwell, Master. Alden Gates | male | 0.8333 | 0.0 | 2.0 | 248738 | 29.0000 | NaN | S | 13 | NaN | Bangkok, Thailand / Roseville, IL |
| 548 | 2.0 | 1.0 | Richards, Master. George Sibley | male | 0.8333 | 1.0 | 1.0 | 29106 | 18.7500 | NaN | S | 4 | NaN | Cornwall / Akron, OH |
| 611 | 3.0 | 1.0 | Aks, Master. Philip Frank | male | 0.8333 | 0.0 | 1.0 | 392091 | 9.3500 | NaN | S | 11 | NaN | London, England Norfolk, VA |
| 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 |
| 590 | 2.0 | 1.0 | West, Miss. Barbara J | female | 0.9167 | 1.0 | 2.0 | C.A. 34651 | 27.7500 | NaN | S | 10 | NaN | Bournmouth, England |
Tych ludzi jeszcze całkiem niedawno można było spotkać na ulicy :)
Pokazanie wykresu kołowego procentowego udziału pasażerów w poszczególnych klasach z uwzględnieniem płci.¶
df = df.dropna(subset=['pclass', 'sex'])
# Grupowanie po klasie i płci a potem zliczenie
class_sex_counts = df.groupby(['pclass', 'sex']).size().unstack()
# Przeliczenie całkowitej ilości pasażerów w każdej z klas
class_totals = df['pclass'].value_counts().sort_index()
# Stworzenie nazw labeli w wartości sumarycznej klas
labels = [f'Class {int(cls)} ({total})' for cls, total in class_totals.items()]
# Narysowanie wykresu z podziałem na płeć
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
class_sex_counts.plot(kind='pie', y='male', labels=labels, autopct='%1.1f%%', ax=axes[0], legend=False)
class_sex_counts.plot(kind='pie', y='female', labels=labels, autopct='%1.1f%%', ax=axes[1], legend=False)
# Ustawienie tytułów
axes[0].set_title('Mężczyźni')
axes[1].set_title('Kobiety')
for ax in axes:
ax.set_ylabel('')
plt.suptitle('Procentowy udział pasażerów z podziałem na klasę podróży i płeć')
plt.tight_layout()
Należy zaznaczyć, że liczby zawarte w nawiasach oznaczają liczbę wszystkich biletów w danej klasie.¶
Wniosek jaki się nasuwa to to, że procentowo i liczebnie najwięcej kobiet oraz mężczyzn podróżowało w klasie 3
Pokaż wykres kołowy śmiertelności do przeżycia dla klasy 1 według podziału na wiek.¶
# Odfiltrowanie klasy 1
class_1_df = df[df['pclass'] == 1.0]
# Podział na wiek
class_1_df['age_group'] = class_1_df['age'].apply(lambda x: 'child' if x < 18 else 'adult')
# Przeliczenie śmiertelności i przeżycia po grupie wiekowej
survived_counts = class_1_df[class_1_df['survived'] == 1].shape[0]
not_survived_children = class_1_df[(class_1_df['survived'] == 0) & (class_1_df['age_group'] == 'child')].shape[0]
not_survived_adults = class_1_df[(class_1_df['survived'] == 0) & (class_1_df['age_group'] == 'adult')].shape[0]
# Przygotowanie danych pod wykres kołowy
total_passengers = class_1_df.shape[0]
not_survived_total = not_survived_children + not_survived_adults
survived_total = total_passengers - not_survived_total
# Zrobienie wykresu
labels = ['Przeżyli', 'Nie przeżyli - dzieci', 'Nie przeżyli - Dorośli']
sizes = [survived_total, not_survived_children, not_survived_adults]
colors = ['#66b3ff', '#ff9999', '#99ff99']
fig, ax = plt.subplots()
ax.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)
ax.axis('equal')
C:\Users\tmirk\AppData\Local\Temp\ipykernel_19720\236861734.py:5: 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 class_1_df['age_group'] = class_1_df['age'].apply(lambda x: 'child' if x < 18 else 'adult')
(np.float64(-1.099999739845874), np.float64(1.099999095319948), np.float64(-1.0999989640946295), np.float64(1.0999999506711728))
Pokaż wykres kołowy śmiertelności do przeżycia dla klasy 2 według podziału na wiek.¶
# Odfiltrowanie klasy 2
class_2_df = df[df['pclass'] == 2.0]
# Podział na wiek
class_2_df['age_group'] = class_2_df['age'].apply(lambda x: 'child' if x < 18 else 'adult')
# Przeliczenie śmiertelności i przeżycia po grupie wiekowej
survived_counts = class_2_df[class_2_df['survived'] == 1].shape[0]
not_survived_children = class_2_df[(class_2_df['survived'] == 0) & (class_2_df['age_group'] == 'child')].shape[0]
not_survived_adults = class_2_df[(class_2_df['survived'] == 0) & (class_2_df['age_group'] == 'adult')].shape[0]
# Przygotowanie danych pod wykres kołowy
total_passengers = class_2_df.shape[0]
not_survived_total = not_survived_children + not_survived_adults
survived_total = total_passengers - not_survived_total
# Zrobienie wykresu
labels = ['Przeżyli', 'Nie przeżyli - dzieci', 'Nie przeżyli - Dorośli']
sizes = [survived_total, not_survived_children, not_survived_adults]
colors = ['#66b3ff', '#ff9999', '#99ff99']
fig, ax = plt.subplots()
ax.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)
ax.axis('equal')
C:\Users\tmirk\AppData\Local\Temp\ipykernel_19720\2736117718.py:5: 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 class_2_df['age_group'] = class_2_df['age'].apply(lambda x: 'child' if x < 18 else 'adult')
(np.float64(-1.0999930334354537), np.float64(1.0999991011840888), np.float64(-1.0999994078064712), np.float64(1.0999999718003082))
Pokaż wykres kołowy śmiertelności do przeżycia dla klasy 3 według podziału na wiek.¶
# Odfiltrowanie klasy 3
class_3_df = df[df['pclass'] == 3.0]
# Podział na wiek
class_3_df['age_group'] = class_3_df['age'].apply(lambda x: 'child' if x < 18 else 'adult')
# Przeliczenie śmiertelności i przeżycia po grupie wiekowej
survived_counts = class_3_df[class_3_df['survived'] == 1].shape[0]
not_survived_children = class_3_df[(class_3_df['survived'] == 0) & (class_3_df['age_group'] == 'child')].shape[0]
not_survived_adults = class_3_df[(class_3_df['survived'] == 0) & (class_3_df['age_group'] == 'adult')].shape[0]
# Przygotowanie danych pod wykres kołowy
total_passengers = class_3_df.shape[0]
not_survived_total = not_survived_children + not_survived_adults
survived_total = total_passengers - not_survived_total
# Zrobienie wykresu
labels = ['Przeżyli', 'Nie przeżyli - dzieci', 'Nie przeżyli - Dorośli']
sizes = [survived_total, not_survived_children, not_survived_adults]
colors = ['#66b3ff', '#ff9999', '#99ff99']
fig, ax = plt.subplots()
ax.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)
ax.axis('equal')
C:\Users\tmirk\AppData\Local\Temp\ipykernel_19720\2790980588.py:5: 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 class_3_df['age_group'] = class_3_df['age'].apply(lambda x: 'child' if x < 18 else 'adult')
(np.float64(-1.0999998381915566), np.float64(1.0999991064053791), np.float64(-1.0999987116082428), np.float64(1.0999999386480115))
WNIOSKI:¶
Niestety największa śmiertelnośc była w klasie 3 potem w klasie 2 a najmniejsza w klasie 1. Równolegle, dzieci miały największą szanse przeżycia w klasie 1.
To potwierdza, że klasa 1 była klasą najbardziej uprzywilejowaną, jednak macierz korelacji ceny biletu do przeżywalności tego nie potwierdza:
# Przeliczenie macierzy
correlation_matrix = df[['pclass', 'fare', 'survived']].corr()
# Wyrysowanie macierzy
plt.figure(figsize=(8, 6))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Correlation Matrix of Fare by Class to Survival')
Porównanie ceny biletu do przeżycia nie wykazuje korelacji, ale ceny biletu do klasy podróży już pokazują lekką korelację.
Gdy rozbije się tę macierz na poszczególne klasy wtedy lepiej widać zależność ceny od śmiertelności jednak wykresy kołowe pokazują więcej.
# Filter out rows with missing values in 'fare', 'pclass', or 'survived'
df_filtered = df.dropna(subset=['fare', 'pclass', 'survived'])
# Create a correlation matrix for each class
correlation_matrices = {}
for pclass in df_filtered['pclass'].unique():
class_data = df_filtered[df_filtered['pclass'] == pclass]
correlation_matrix = class_data[['fare', 'survived']].corr()
correlation_matrices[pclass] = correlation_matrix
# Plot the correlation matrices
fig, axes = plt.subplots(1, len(correlation_matrices), figsize=(15, 5))
fig.suptitle('Korelacja ceny biletu do przeżywalności według klas')
for ax, (pclass, corr_matrix) in zip(axes, correlation_matrices.items()):
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', ax=ax)
ax.set_title(f'Class {int(pclass)}')
WNIOSKI:¶
- w klasie 1:
śmiertelność dorosłych wyniosła: 35.6%
śmiertelność dzieci wyniosła: 0.7%
- w klasie 2:
śmiertelność dorosłych wyniosła: 54.4%
śmiertelność dzieci wyniosła: 1.5%
- w klasie 3:
śmiertelność dorosłych wyniosła: 60.5%
śmiertelność dzieci wyniosła: 13.4%
Wniosek jaki się sam nasuwa to taki, że wykres zależności ceny od przeżywalności jest nieadekwatny i żeby uzyskać bardziej wiarygodne dane należy posłużyć sie wykresem kołowym osobnym dla każdej z klas. Wtedy na własne oczy możemy ujrzeć jak w rzeczywistości wyglądała sytuacja i gdzie była największa śmiertelność.¶