これは数年前に赤石雅典先生の著書『最短コースでわかる Pythonプログラミングとデータ分析』を読んだ際に取ったメモの一部です。
私が赤石先生の分析に基づいて、自分が整理して、半分以上の内容を追加しました。
皆さんの参考になれば幸いです。
参考として、次の記事も参照してください。
データサイエンティストに必要な最低限のPython知識
データサイエンティストのためのPythonライブラリ応用(入門編)
データサイエンティストのためのPythonライブラリ応用(中級編)
データサイエンティストのためのPythonライブラリ応用(上級編)
導読:分析の全体像と認知マップ
分析対象
1912年に大西洋上で沈没したタイタニック号の乗客データ(約1,309名)を用い、「どのような乗客が生存しやすかったのか」 を多角的に分析する。データサイエンスの実践的な分析フローを一通り体験できる、定番の教材データセットである。
主要な変数(列)一覧
| 変数名 | 英語名 | 型 | 内容 |
|---|---|---|---|
| 客室クラス | pclass | 整数 | 1等(1)、2等(2)、3等(3) |
| 生存状況 | survived | 整数 | 生存(1)、死亡(0) |
| 性別 | sex | 文字列 | male / female |
| 年齢 | age | 小数 | 0.17〜80歳(欠損値あり) |
| 運賃 | fare | 小数 | 0〜512ポンド |
| 乗船港 | embarked | 文字列 | C(シェルブール)、Q(クイーンズタウン)、S(サウサンプトン) |
| 兄弟_配偶者数 | sibsp | 整数 | 同乗の兄弟・配偶者の数 |
| 親_子供数 | parch | 整数 | 同乗の親・子供の数 |
分析の目的
- 事故時の生存率を決定した要因を特定する
- 各変数が生存に与える影響を定量的に評価する
- 相関や傾向を可視化し、洞察を得る
分析フロー(本文の構成)
本文は、データ分析の標準的なワークフローに沿って以下の6ステップで構成されている。
| ステップ | セクション | やること | 主な手法・関数 |
|---|---|---|---|
| ① | 1. データ読み込み | CSVの取得・読み込み・型問題の解決 |
!wget, pd.read_csv(na_values, quotechar, escapechar, dtype), df.info()
|
| ② | 2. データ確認・加工 | 型確認・列名変更・欠損値確認・統計量・特徴量作成 |
dtypes, isnull().sum(), describe(), value_counts(), pd.cut(), pd.qcut(), str.extract()
|
| ③ | 3. データ集計 | カテゴリ別集計・クロス集計で傾向を数値化 |
groupby().mean(), pd.crosstab(), pivot_table(), agg()
|
| ④ | 4. データ可視化 | 分布・関係性をグラフで視覚的に把握 |
df.hist(), sns.boxplot(), sns.heatmap(), corr()
|
| ⑤ | 5. 仮説と検証 | 各要因ごとの生存率を検証し、因果を探る |
groupby().mean().plot(), sns.histplot(), pivot_table() + sns.heatmap()
|
| ⑥ | 6. 深掘り分析 | 例外ケース分析・要因の総合ランキング |
query(), notnull(), corr() の絶対値ランキング |
読み方のヒント:①→②で「データを正しく読み込み、分析できる状態に整える」。③→④で「数値とグラフで全体傾向を掴む」。⑤→⑥で「仮説を立てて検証し、洞察を深める」。この3段階を意識すると全体の流れが掴みやすい。
得られる主な知見(想定)
- 性別が最大の生存要因(女性の生存率 ≈ 73%、男性 ≈ 21%)
- 客室クラスが高いほど生存率が高い(1等 ≈ 62%、3等 ≈ 25%)
- 年齢では子供(0〜12歳)の生存率が相対的に高い
- 家族人数2〜4人が最適、単独・大家族は低い
- 乗船港の生存率差は、客室クラス構成比の違いによる疑似相関
- 複数要因の組み合わせ(性別×クラス×年齢)で生存率が大きく変わる
共通処理
- データ分析プロジェクトの冒頭で、必要なライブラリのインポートと表示オプションの初期設定を行う。
-
japanize_matplotlibをインストールすることで、Matplotlibのグラフ上で日本語が文字化けせずに表示される。 -
seabornはMatplotlibを基盤とした高水準の可視化ライブラリで、統計的なグラフを簡潔なコードで描画できる。
# 日本語化ライブラリ導入
!pip install japanize-matplotlib | tail -n 1
# ライブラリのimport
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd
from IPython.display import display
import seaborn as sns
# 表示オプション調整
np.set_printoptions(suppress=True, precision=4, floatmode='fixed')
plt.rcParams["font.size"] = 14
plt.rcParams['figure.figsize'] = (6, 6)
plt.rcParams['axes.grid'] = True
pd.options.display.float_format = '{:.4f}'.format
pd.set_option("display.max_columns", None)
1. データ読み込み
ファイルダウンロード
-
!wgetコマンドでインターネット上のCSVファイルをローカルにダウンロードする。 -
-nc(no-clobber)オプションを付けると、既にファイルが存在する場合はダウンロードをスキップし、重複取得を防ぐ。
url = 'https://raw.githubusercontent.com/makaishi2/samples/main/data/titanic-v2.csv'
!wget -nc $url
ファイル内容確認
-
!headコマンドでファイルの先頭数行を確認し、区切り文字(カンマ、セミコロン等)やクオート文字(シングル・ダブル)、ヘッダの有無などの特徴を事前に把握する。 - データの構造を確認してから
read_csvのオプションを決定するのが、正確な読み込みへの近道。
csv_fn = 'titanic-v2.csv'
# 先頭3行を確認(ヘッダ行+データ2行)
!head -3 $csv_fn
データ読み込み(段階的な問題解決)
- 実務では、CSVの読み込みは一度で完璧にいかないことが多い。エラーや不正データに遭遇するたびにオプションを追加し、段階的に問題を解決していく。
- このタイタニック・データセットでは、3回の試行を経て正しくデータを読み込む過程を学ぶ。
-
1回目:基本オプション(
na_values,quotechar)で読み込み、問題が起きたら原因を調査。
# 1回目:まず基本オプションで試す
df = pd.read_csv(
csv_fn,
na_values='?', # '?' を欠損値として扱う
quotechar="'") # シングルクオートで囲まれた文字列
-
問題判別:エラーが発生した行を
!headと!tailで確認する。
# 問題の起きた行を確認(130行目付近を表示)
!head -130 $csv_fn | tail -1
-
2回目:エスケープ文字(
escapechar)を追加して、名前中のバックスラッシュ問題を解決する。
# 2回目:escapechar オプションを追加
df = pd.read_csv(
csv_fn,
na_values='?',
quotechar="'",
escapechar='\\') # バックスラッシュをエスケープ文字に指定
display(df.head(1))
-
3回目:データ型の問題を確認し、
dtypeで特定列の型を明示指定する。
# 3回目:dtype オプションで body 列を文字列型として強制指定
df = pd.read_csv(
csv_fn,
na_values='?',
quotechar="'",
escapechar='\\',
dtype={'body': object}) # body列を文字列型で読み込む
# データ型の最終確認
print(df.dtypes)
display(df[['body']].head())
データ件数と形状の確認
- 読み込み直後に
shape(行数×列数のタプル)とinfo()(各列の非null件数・データ型の一覧)でデータの全体像を把握する。 -
info()は欠損値の有無やメモリ使用量も表示するため、データ品質の初期チェックに最適。
# 行数×列数
print(f'データ形状: {df.shape[0]}行 × {df.shape[1]}列')
# 各列の非null件数・データ型を一覧表示
df.info()
2. データ確認・加工
データ型確認
-
dtypes属性で各列のデータ型を確認し、意図しない型(例:本来整数の列が浮動小数点型になっている等)がないかチェックする。 - 浮動小数点型への意図しない変換は、多くの場合、欠損値(NaN)の存在が原因である。
df.dtypes
項目名変更
- 英語の項目名を日本語に変更して、分析結果やグラフの可読性を高める。
-
df.columnsに新しい列名のリストを代入することで、一括で列名を変更できる。列数と名前リストの要素数は一致させる必要がある。
columns = [
'客室クラス', '生存状況', '氏名', '性別',
'年齢', '兄弟_配偶者数', '親_子供数',
'乗船券番号', '運賃', '客室番号', '乗船港',
'救命ボート番号', '遺体識別番号', '自宅または目的地'
]
df.columns = columns
display(df.head(2))
欠損値確認
-
isnull().sum()で各項目の欠損値件数を一覧表示する。欠損率も同時に計算すると、データ品質を定量的に評価できる。 - 欠損値の多い列(年齢:約263件、客室番号:約823件)は、分析や可視化で結果が偏る可能性があるため注意が必要。
- 欠損値の対処法としては、除去(
dropna)、補完(平均値・中央値・最頻値での埋め合わせ)、そのまま放置の3通りがある。
# 欠損値の件数と割合を同時に確認
null_info = pd.DataFrame({
'欠損数': df.isnull().sum(),
'欠損率(%)': (df.isnull().sum() / len(df) * 100).round(2)
})
display(null_info[null_info['欠損数'] > 0])
統計量計算
-
describe()で数値型項目の基本統計量(件数count、平均mean、標準偏差std、最小min、25%/50%/75%四分位数、最大max)を一括取得する。 -
describe(include=['O'])で文字列型(object型)項目の統計量(件数count、ユニーク数unique、最頻値top、最頻値の出現回数freq)を取得する。 - 平均と中央値(50%)の乖離が大きい場合、データに外れ値や偏りがある可能性を示す。
# 数値項目の統計量(件数、平均、標準偏差、最小、四分位、最大)
df.describe()
# 文字列型項目の統計量(件数、ユニーク数、最頻値、頻度)
df.describe(include=['O'])
値の出現回数確認
- カテゴリ項目の値分布を
value_counts()で確認し、データの偏り(クラス不均衡など)を把握する。 - 例えば、生存状況は死亡者が生存者より多く(約2:1)、性別は男性が女性より多い。このような偏りは分析結果の解釈に影響する。
target_cols = ['客室クラス', '生存状況', '性別', '乗船港']
for c in target_cols:
print(f'--- {c} ---')
print(df[c].value_counts())
print()
特徴量エンジニアリング(新しい項目の追加)
-
既存の項目を組み合わせたり変換したりして、分析に有用な新しい特徴量を作成する。
-
特徴量エンジニアリングは、データ分析や機械学習において分析精度を向上させる最も重要な工程の一つ。
-
家族人数:兄弟_配偶者数と親_子供数を合算し、本人を加えて家族人数を作成する。
# 家族人数 = 兄弟配偶者数 + 親子供数 + 本人(1)
df['家族人数'] = df['兄弟_配偶者数'] + df['親_子供数'] + 1
# 単独乗船かどうかのフラグ
df['単独乗船'] = (df['家族人数'] == 1).astype(int)
display(df[['兄弟_配偶者数', '親_子供数', '家族人数', '単独乗船']].head())
-
年齢区分:
pd.cut()を使い、年齢を意味のあるグループ(子供・若年・中年・高齢)にカテゴリ分けする。連続値をカテゴリに変換することで、グループ間の生存率比較が容易になる。
# 年齢区分を作成(pd.cutで区間分割)
df['年齢区分'] = pd.cut(
df['年齢'],
bins=[0, 12, 30, 50, 80],
labels=['子供(0-12)', '若年(13-30)', '中年(31-50)', '高齢(51-80)'])
print(df['年齢区分'].value_counts())
-
運賃区分:
pd.qcut()を使い、運賃を四分位数に基づいて4段階にカテゴリ分けする。pd.cut()が等幅分割であるのに対し、pd.qcut()は各ビンに同数のデータが入るように分割する。
# 運賃区分を作成(四分位数で区間分割、欠損値はNaNのまま保持)
df['運賃区分'] = pd.qcut(
df['運賃'], q=4,
labels=['低額', '中低額', '中高額', '高額'])
print(df['運賃区分'].value_counts())
-
敬称抽出:
str.extract()と正規表現を使い、氏名から敬称(Mr, Mrs, Miss, Master等)を抽出する。敬称は性別・年齢・社会的地位を間接的に反映する複合指標として有用。
# 氏名から敬称を正規表現で抽出
df['敬称'] = df['氏名'].str.extract(r',\s*(\w+)\.')
# 敬称ごとの件数(上位を確認)
print(df['敬称'].value_counts())
3. データ集計
グループごとの集計
-
groupby().mean()でカテゴリ別の平均値を算出する。生存状況は0/1の値なので、平均値がそのまま生存率になる。 -
numeric_only=Trueを指定して、数値型の列のみを集計対象にする(文字列型の列でエラーが出るのを防ぐ)。
# 客室クラスごとの平均値(生存率、年齢、運賃など)
df.groupby('客室クラス').mean(numeric_only=True)
- 性別ごとの集計も合わせて確認する。
# 性別ごとの平均値
df.groupby('性別').mean(numeric_only=True)
出現頻度のクロス集計
-
pd.crosstab()で2つのカテゴリ項目の出現頻度をクロス集計する。行と列の交差セルに、該当するレコード数が表示される。 -
margins=Trueを指定すると、各行・各列の合計値(All行/列)が追加される。
# 「客室クラス」×「乗船港」の出現頻度
df_crosstab = pd.crosstab(
index=df['客室クラス'],
columns=df['乗船港'],
margins=True)
display(df_crosstab)
項目値のクロス集計
-
pivot_table()で2軸(行軸・列軸)を指定し、特定の数値項目について集約関数(平均mean、合計sum等)を適用したクロス集計を行う。 -
crosstabが出現回数を数えるのに対し、pivot_tableは任意の値を集約できる点が異なる。
# 「性別」×「客室クラス」を軸とした生存率の集計
df_pivot = df.pivot_table(
index='性別',
columns='客室クラス',
values='生存状況',
aggfunc='mean')
display(df_pivot)
年齢区分ごとの生存率集計
- 年齢区分と客室クラスを組み合わせたクロス集計で、年齢と社会的地位の交互作用が生存率に与える影響を確認する。
# 年齢区分 × 客室クラスの生存率
df_pivot_age = df.pivot_table(
index='年齢区分',
columns='客室クラス',
values='生存状況',
aggfunc='mean')
display(df_pivot_age)
家族人数ごとの生存率集計
- 家族人数が生存にどう影響するかを集計で確認する。
agg()メソッドで複数の集約関数を同時に適用し、生存率と人数を一度に取得できる。
# 家族人数ごとの生存率と人数
df_family = df.groupby('家族人数').agg(
生存率=('生存状況', 'mean'),
人数=('生存状況', 'count')
)
display(df_family)
敬称ごとの生存率集計
- 敬称(社会的属性)と生存率の関係を確認する。Mr(成人男性)の生存率が最も低く、Mrs/Miss(女性)は高い。Master(少年)も比較的高い生存率を示す。
# 敬称ごとの生存率・平均年齢・人数
df_title = df.groupby('敬称').agg(
生存率=('生存状況', 'mean'),
平均年齢=('年齢', 'mean'),
人数=('生存状況', 'count')
).sort_values('人数', ascending=False)
display(df_title.head(10))
4. データ可視化
数値項目のヒストグラム表示
-
df.hist()でデータフレーム中の全数値項目のヒストグラムを一括表示し、各項目の分布形状(正規分布、偏り、外れ値など)を視覚的に把握する。 -
binsでビン数を増やすと分布の詳細が見えるが、多すぎるとノイズが目立つ。20前後が目安。
plt.rcParams['figure.figsize'] = (10, 6)
df.hist(bins=20, layout=(2, 3))
plt.tight_layout()
plt.show()
- 特定の項目について範囲を絞って詳細な分布を確認する。
plt.rcParams['figure.figsize'] = (4, 4)
df['運賃'].hist(bins=60)
plt.xlim(0, 150)
plt.title('運賃詳細分布')
plt.show()
非数値項目の度数分布
-
value_counts().plot(kind='bar')でカテゴリ変数の各値の出現回数を棒グラフで可視化する。 -
enumerateでインデックスと値を同時に取得し、subplotと組み合わせることで複数のカテゴリ項目を横並びで表示できる。
plt.rcParams['figure.figsize'] = (8, 4)
cat_cols = ['性別', '乗船港']
for i, c in enumerate(cat_cols):
ax = plt.subplot(1, 2, i + 1)
df[c].value_counts().plot(kind='bar', title=c, ax=ax)
plt.tight_layout()
plt.show()
箱ひげ図
-
sns.boxplot()でカテゴリ別の数値分布(中央値、四分位範囲、外れ値)を比較する。箱の中の線が中央値、箱の上下端が第1・第3四分位数、ひげの先が外れ値境界を示す。 - 1等客室は運賃の中央値・分散ともに大きく、外れ値(超高額運賃)も存在する。
plt.rcParams['figure.figsize'] = (6, 6)
sns.boxplot(
x='客室クラス', y='運賃', hue='客室クラス', data=df,
palette=['blue', 'cyan', 'grey'], legend=False)
plt.title('客室クラスと運賃の関係')
plt.show()
# y軸を制限して詳細確認
sns.boxplot(
x='客室クラス', y='運賃', hue='客室クラス', data=df,
palette=['blue', 'cyan', 'grey'], legend=False)
plt.title('客室クラスと運賃の関係(拡大版)')
plt.ylim(0, 120)
plt.show()
- 客室クラス×生存状況別の年齢分布を箱ひげ図で比較する。
plt.rcParams['figure.figsize'] = (8, 5)
sns.boxplot(
x='客室クラス', y='年齢', hue='生存状況', data=df,
palette=['salmon', 'lightgreen'])
plt.title('客室クラス・生存状況別の年齢分布')
plt.show()
ヒートマップ
-
pivot_tableの結果をsns.heatmap()で可視化する。色の濃淡で数値の大小を直感的に把握でき、2軸のクロス集計結果を視覚化するのに最適。 -
annot=Trueでセル内に数値を表示、fmtで表示形式(.03f=小数3桁)を指定する。
df_pivot = df.pivot_table(
index='性別', columns='客室クラス',
values='生存状況', aggfunc='mean')
plt.rcParams['figure.figsize'] = (4, 4)
sns.heatmap(
df_pivot, square=True, annot=True,
fmt='.03f', cmap='Blues', cbar=False)
plt.title(f'{df_pivot.columns.name} vs {df_pivot.index.name}')
plt.xlabel(df_pivot.columns.name)
plt.ylabel(df_pivot.index.name)
plt.show()
数値項目間の相関ヒートマップ
-
corr()メソッドで数値項目間のピアソン相関係数を計算し、生存状況と強い相関を持つ要因を特定する。 - 相関係数は-1〜1の範囲で、1に近いほど正の相関、-1に近いほど負の相関、0は相関なし。
- 注意:相関は因果関係を意味しない。あくまで変数間の線形な関連性の指標。
# 数値項目間の相関行列
corr_cols = ['生存状況', '客室クラス', '年齢', '兄弟_配偶者数',
'親_子供数', '運賃', '家族人数', '単独乗船']
corr_matrix = df[corr_cols].corr()
plt.rcParams['figure.figsize'] = (8, 6)
sns.heatmap(
corr_matrix, annot=True, fmt='.2f',
cmap='RdBu_r', center=0, square=True,
vmin=-1, vmax=1)
plt.title('数値項目間の相関ヒートマップ')
plt.tight_layout()
plt.show()
5. 仮説と検証
生存状況と性別の関係
- 女性の生存率は約73%で、男性の約21%を大幅に上回る。これは当時の「女性と子供を優先」という避難方針を反映している。
-
groupby('性別')['生存状況'].mean()で性別ごとの生存率(0/1の平均=割合)を算出し、棒グラフで可視化する。
plt.rcParams['figure.figsize'] = (4, 4)
df.groupby('性別')['生存状況'].mean().plot(kind='bar')
plt.title('性別ごとの生存率')
plt.ylabel('生存率')
plt.show()
生存状況と年齢の関係
-
sns.histplot()のhueパラメータで生存/死亡に色分けし、年齢分布の違いを視覚的に比較する。 -
multiple='dodge'で棒を横に並べて表示し、各年齢帯での生存・死亡の比率が読み取りやすくなる。
plt.rcParams['figure.figsize'] = (8, 4)
sns.histplot(
data=df, x='年齢', hue='生存状況',
palette=['blue', 'cyan'], multiple='dodge',
shrink=0.7)
plt.title('生存状況と年齢の関係')
plt.show()
- 年齢区分ごとの生存率を棒グラフで確認する。子供の優先避難が読み取れる。
plt.rcParams['figure.figsize'] = (6, 4)
df.groupby('年齢区分')['生存状況'].mean().plot(kind='bar', color='steelblue')
plt.title('年齢区分ごとの生存率')
plt.ylabel('生存率')
plt.xticks(rotation=0)
plt.show()
生存状況と客室クラスの関係
- 1等客室の生存率は約62%、2等は約41%、3等は約25%と、客室クラスが高いほど生存率が高い。
- 上層の客室は救命ボートに近い上層デッキにあり、避難がしやすかったことが一因と考えられる。
plt.rcParams['figure.figsize'] = (4, 4)
df.groupby('客室クラス')['生存状況'].mean().plot(kind='bar')
plt.title('客室クラスごとの生存率')
plt.ylabel('生存率')
plt.show()
生存状況と乗船港の関係
- 乗船港(C=シェルブール、Q=クイーンズタウン、S=サウサンプトン)ごとの生存率に差がある。
- Cの生存率が最も高いが、これは乗船港自体が原因ではなく、乗船港と客室クラスの交絡因子の影響(次節で検証)。
plt.rcParams['figure.figsize'] = (4, 4)
df.groupby('乗船港')['生存状況'].mean().plot(kind='bar')
plt.title('乗船港ごとの生存率')
plt.ylabel('生存率')
plt.show()
乗船港で生存状況が異なる理由の分析
-
normalize='columns'で列方向に正規化し、各港における客室クラスの構成比を算出する。 - シェルブール港(C)は1等客室の乗客比率が約50%と高く、結果的にC港全体の生存率が高くなっている(疑似相関)。
df_crosstab = pd.crosstab(
index=df['客室クラス'],
columns=df['乗船港'],
normalize='columns')
plt.rcParams['figure.figsize'] = (4, 4)
sns.heatmap(
df_crosstab, square=True, annot=True,
fmt='.03f', cmap='Blues', cbar=False)
plt.title('乗船港ごとの客室クラス構成比')
plt.show()
生存状況と運賃の関係
- 運賃区分ごとの生存率を確認する。高額運賃の乗客ほど生存率が高い。これは運賃が高い乗客が上位の客室クラスに属し、救命ボートへのアクセスが容易だったためと考えられる。
plt.rcParams['figure.figsize'] = (6, 4)
df.groupby('運賃区分')['生存状況'].mean().plot(kind='bar', color='teal')
plt.title('運賃区分ごとの生存率')
plt.ylabel('生存率')
plt.xticks(rotation=0)
plt.show()
生存状況と家族人数の関係
- 単独乗船者は助け合う家族がおらず、大家族(7人以上)は全員分のボート確保が困難で、いずれも生存率が低い。
- 2〜4人程度の家族が最も生存率が高く、適度な家族規模が避難時に有利に働いたと推測される。
plt.rcParams['figure.figsize'] = (8, 4)
df.groupby('家族人数')['生存状況'].mean().plot(kind='bar', color='darkcyan')
plt.title('家族人数ごとの生存率')
plt.ylabel('生存率')
plt.xlabel('家族人数')
plt.show()
- 単独乗船かどうかで生存率を比較する。
plt.rcParams['figure.figsize'] = (4, 4)
df.groupby('単独乗船')['生存状況'].mean().plot(
kind='bar', color=['steelblue', 'salmon'])
plt.title('単独乗船 vs 家族連れの生存率')
plt.ylabel('生存率')
plt.xticks([0, 1], ['家族連れ', '単独'], rotation=0)
plt.show()
生存状況と敬称の関係
- 敬称は性別・年齢・社会的地位を反映する複合指標として有用。
plt.rcParams['figure.figsize'] = (10, 4)
# 主要な敬称のみに絞って生存率を表示
main_titles = ['Mr', 'Mrs', 'Miss', 'Master', 'Dr', 'Rev']
df_main = df[df['敬称'].isin(main_titles)]
df_main.groupby('敬称')['生存状況'].mean().reindex(
main_titles).plot(kind='bar', color='darkorange')
plt.title('敬称ごとの生存率')
plt.ylabel('生存率')
plt.xticks(rotation=0)
plt.show()
性別×客室クラス×年齢区分の三要素分析
- 単一要因だけでなく、複数要因の組み合わせが生存率にどう影響するかを分析する。
- まず性別×年齢区分の2軸ヒートマップで傾向を確認し、さらに客室クラスで分割することで、3要素の交互作用を可視化する。
# 性別×年齢区分の生存率ヒートマップ
df_pivot3 = df.pivot_table(
index='年齢区分', columns='性別',
values='生存状況', aggfunc='mean')
plt.rcParams['figure.figsize'] = (4, 4)
sns.heatmap(
df_pivot3, annot=True, fmt='.03f',
cmap='YlOrRd', cbar=False, square=True)
plt.title('性別 × 年齢区分の生存率')
plt.show()
- facetgrid で客室クラスごとに性別×年齢の生存率を可視化する。
plt.rcParams['figure.figsize'] = (12, 4)
for i, cls in enumerate([1, 2, 3]):
ax = plt.subplot(1, 3, i + 1)
tmp = df[df['客室クラス'] == cls].pivot_table(
index='年齢区分', columns='性別',
values='生存状況', aggfunc='mean')
sns.heatmap(tmp, annot=True, fmt='.2f',
cmap='YlOrRd', cbar=False, ax=ax, vmin=0, vmax=1)
ax.set_title(f'{cls}等客室')
plt.tight_layout()
plt.show()
6. 深掘り分析
項目「救命ボート」の追加
-
notnull()メソッドで救命ボート番号の有無をブール値(True/False)に変換し、新しい列として追加する。 - 救命ボートに乗れたかどうかは生存に最も直結する要因であり、この項目を軸にした深掘り分析で、例外的なケース(ボートなしで生存、ボートありで死亡)の特徴を明らかにする。
df['救命ボート'] = df['救命ボート番号'].notnull()
display(df[['救命ボート番号', '救命ボート']].head(3))
「救命ボート」「生存状況」を軸とした出現頻度分析
- 救命ボートの有無と生存状況のクロス集計で、ボートと生存の関係を定量化する。
df_crosstab = pd.crosstab(
index=df['生存状況'],
columns=df['救命ボート'])
display(df_crosstab)
救命ボート乗船率の分析
- 性別・客室クラス別に救命ボートに乗れた割合をヒートマップで確認する。
- 女性かつ1等客室の乗船率が最も高く、男性かつ3等客室が最も低い。「女性と子供優先」と「上位クラス優先」の方針が数値に表れている。
# 性別 × 客室クラス別の救命ボート乗船率
df_boat_rate = df.pivot_table(
index='性別', columns='客室クラス',
values='救命ボート', aggfunc='mean')
plt.rcParams['figure.figsize'] = (4, 4)
sns.heatmap(
df_boat_rate, annot=True, fmt='.03f',
cmap='Greens', cbar=False, square=True)
plt.title('性別×客室クラス別 救命ボート乗船率')
plt.show()
救命ボートなしで助かった人
-
queryで「生存かつ救命ボートなし」の乗客を抽出する。
x1 = df.query('生存状況 == 1 and 救命ボート == False')
display(x1[['客室クラス', '生存状況', '氏名', '性別', '年齢',
'救命ボート番号']].head(3))
- 救命ボートなしで生存した人の性別分布と生存率を確認する。
print(x1['性別'].value_counts())
print()
x_no_boat = df.query('救命ボート == False')
print(x_no_boat.groupby('性別')['生存状況'].mean())
救命ボートに乗れたのに助からなかった人
-
queryで「死亡かつ救命ボートあり」の乗客を抽出する。
x2 = df.query('生存状況 == 0 and 救命ボート == True')
display(x2[['客室クラス', '生存状況', '氏名', '性別', '年齢',
'救命ボート番号']].head(3))
- 救命ボートに乗っても助からなかった割合を性別で集計する。
print(x2['性別'].value_counts())
print()
x_boat = df.query('救命ボート == True')
print(1 - x_boat.groupby('性別')['生存状況'].mean())
生存要因の総合まとめ
- 全要因の生存率への影響度を一覧で比較し、どの要因が最も生存に影響したかを総合的に評価する。
# 各カテゴリ別の生存率を一覧にまとめる
for col in ['性別', '客室クラス', '乗船港', '年齢区分', '運賃区分', '単独乗船']:
print(f'\n=== {col} ===')
result = df.groupby(col)['生存状況'].agg(['mean', 'count'])
result.columns = ['生存率', '人数']
display(result.sort_values('生存率', ascending=False))
- 数値項目と生存状況の相関係数の絶対値を水平棒グラフでランキング表示し、どの数値要因が生存に最も関連しているかを定量的に比較する。
# 生存状況との相関係数をランキング表示
corr_with_survival = df[corr_cols].corr()['生存状況'].drop('生存状況')
corr_sorted = corr_with_survival.abs().sort_values(ascending=True)
plt.rcParams['figure.figsize'] = (6, 4)
corr_sorted.plot(kind='barh', color='steelblue')
plt.title('生存状況との相関の強さ(絶対値)')
plt.xlabel('相関係数の絶対値')
plt.tight_layout()
plt.show()
バージョン確認
-
watermarkパッケージで利用中のライブラリのバージョンを一括表示する。分析の再現性を確保するために、使用したライブラリのバージョンを記録しておくことが重要。
!pip install watermark | tail -n 1
%load_ext watermark
%watermark --iversions