自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

(その2-1) タイタニックのデータ俯瞰

Data Analytics
Data Analytics

前回の記事からの続きです。

(その1) タイタニックのデータセットの分析計画
ヒノマルク レオナルド・ディカプリオ主演の映画タイタニックをご覧になったことがある方はイメージがつくと思います。 テレビでもよく金曜ロードショーで放映されますね。 ヒノマルクは好きでもう何回も見ています。 Wikipediaのタイタニックの...

今回はタイタニックのデータの中身を俯瞰しようと思います。

単純にクロス集計をして表を眺めるだけでもいいのですが、ヒストグラムや散布図などで可視化してあげるとより理解しやすくなると思います。

タイタニックのデータぐらいのカラム数なら問題ないのですが、実業務で変数の数が多くなると見ないといけない数字やグラフの数も増えるので大変になります。

変数選択など経て変数の数を減らしてからデータの中身を見た方が実は効率がいいのかも知れません。

データ確認の結果が少し長くなってしまったので3つの記事に分割して公開します。

(その2-2)はこちら

スポンサーリンク

② データの理解

カテゴリ型の変数と数値型の変数

前回変数の一覧をまとめてみましたが、どの変数がカテゴリ型なのか数値型なのかまとめてみました。
色々呼び方はあるかと思います。質的変数・量的変数とか、名義型・離散型・連続型などいずれかは統一した呼び方をするためにまとめたいと思います。

カテゴリ型の変数

  • Survived (0:非生存、1:生存)
  • Pclass (1:1st(上級クラス)、2:2nd(中級クラス)、3:3rd(下級クラス))
  • Sex (1:男性、2:女性)
  • embarked (C:Cherbourg、Q:Queenstown、S:Southampton)

数値型の変数

  • Age (年齢)
  • Fare (運賃)
  • SibSp (タイタニックに乗船した兄弟・姉妹・配偶者の数)
  • Parch (タイタニックに乗船した両親・子供の数)

※ ほとんどのケースでツール側でデータの中身をみてどのデータ型か判別してくれることが多いですが、分析の目的に合わせて文字列ではなく数値で読み込んだりする場合もあります。今回は上記のようにまとめてみました。名前は名義型でしょうか?PassengerIdは数値型に入るのでしょうか?とりあえず分析に使いそうな項目だけ分類しました。

データ俯瞰

データの読み込み

import pandas as pd
# タイタニックデータセットの訓練データを読み込み
df = pd.read_csv("/Users/hinomaruc/Desktop/notebooks/titanic/train.csv")
df
Out[0]
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
... ... ... ... ... ... ... ... ... ... ... ... ...
886 887 0 2 Montvila, Rev. Juozas male 27.0 0 0 211536 13.0000 NaN S
887 888 1 1 Graham, Miss. Margaret Edith female 19.0 0 0 112053 30.0000 B42 S
888 889 0 3 Johnston, Miss. Catherine Helen "Carrie" female NaN 1 2 W./C. 6607 23.4500 NaN S
889 890 1 1 Behr, Mr. Karl Howell male 26.0 0 0 111369 30.0000 C148 C
890 891 0 3 Dooley, Mr. Patrick male 32.0 0 0 370376 7.7500 NaN Q

891 rows × 12 columns

# カラム名を確認
df.columns
Out[0]
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
           'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'],
          dtype='object')

データ件数の確認

# 最大レコード数を確認。indexが0から始まるので、+1している。
df.index.max() + 1
Out[0]
891

タイタニックの乗客が約2224人なので、その内891人のデータが学習用データのようです。
891/2224 = 0.40なので全体の約40%のサンプルになるようです。

各カラムのレコード数の確認 (NULLはカウントしない)

# 各カラムのレコード数の確認 (NULLはカウントされない)
df.count()
Out[0]
PassengerId    891
Survived       891
Pclass         891
Name           891
Sex            891
Age            714
SibSp          891
Parch          891
Ticket         891
Fare           891
Cabin          204
Embarked       889
dtype: int64

Age,Cabin,EmbarkedでNULLが存在するようです。
欠損値処理で平均、中央値、最頻値などに置換する必要がありそうです。
Cabinは欠損値が多いのでそもそも利用しないという手もあります。

infoメソッドの利用

全体や各変数のレコード数を確認したい場合はdf.info()でも確認できるようです。

# infoメソッドでも大体のことが分かる
df.info()
Out[0]
    RangeIndex: 891 entries, 0 to 890
    Data columns (total 12 columns):
     #   Column       Non-Null Count  Dtype  
    ---  ------       --------------  -----  
     0   PassengerId  891 non-null    int64  
     1   Survived     891 non-null    int64  
     2   Pclass       891 non-null    int64  
     3   Name         891 non-null    object
     4   Sex          891 non-null    object
     5   Age          714 non-null    float64
     6   SibSp        891 non-null    int64  
     7   Parch        891 non-null    int64  
     8   Ticket       891 non-null    object
     9   Fare         891 non-null    float64
     10  Cabin        204 non-null    object
     11  Embarked     889 non-null    object
    dtypes: float64(2), int64(5), object(5)
    memory usage: 83.7+ KB

欠損値の確認

次に重要な欠損値の確認です。

# 欠損値の確認
chk_null = df.isnull().sum()
chk_null[chk_null > 0]
Out[0]
Age         177
Cabin       687
Embarked      2
dtype: int64

欠損があるレコード数が表示されました。
数値だけだと規模感が分かりづらいので、割合も出してみます。

# 欠損値割合の確認
chk_null_pct = chk_null / (df.index.max() + 1)
chk_null_pct[chk_null_pct > 0]
Out[0]
Age         0.198653
Cabin       0.771044
Embarked    0.002245
dtype: float64

Ageが約20%、Cabinが約77%欠損していることが分かりました。

モデル作成時にCabinは欠損割合が高いのでやはり利用は躊躇すると思います。

Ageは悩みます。とても重要なファクターな気もするので、上手く他の変数から年齢を推定できないかどうか検討するかもしれません。

データ重複の確認

重複データがあるかどうか確認します。

キーだと思われる変数に重複があったりすると結合処理などが上手く出来ない場合があるので確認してみます。

# 最大重複数を出力
print("最大重複レコード数 (1=重複なし)")

chk_duplicates =  df.dtypes[df.dtypes == 'object']

for idx in chk_duplicates.index: # indexでloopする
# for val in chk_duplicates: # valueでloopする
# for idx,val in chk_duplicates.items(): # index,valueでloopする
    print(idx,"->",df.groupby(idx).PassengerId.nunique().max())
Out[0]
    最大重複レコード数 (1=重複なし)
    Name -> 1
    Sex -> 577
    Ticket -> 7
    Cabin -> 4
    Embarked -> 644

Nameは重複なしのようです。

TicketやCabinは若干重複しているデータがあるようです。
同室に泊まっている家族のデータは同じTicketやCabinの番号になっているのかも知れません。

SexやEmbarkedはカラム定義から推測するにカテゴリ値なので重複データになっていても問題ありません。

ちなみに、重複データの確認はpandasのdescribeメソッドでも確認することが出来ます。
わざわざfor loopを使って確認しなくても良いかもしれません。

記述統計の確認 (数値型)

pandasには記述統計をお手軽に確認できるdescribeメソッドが用意されています。

デフォルトだと数値型以外の変数の統計量(最頻値など)は出てきませんが、df.describe(include=['O'])のようにオプションを設定することによって質的データの統計も確認することが可能になります。

またパーセンタイル値もデフォルトだと[0,0.25,0.5,0.75,1]しか表示されませんが、こちらもpercentiles=[0.1,0.2,0.3]のように設定が可能なようです。

# describeメソッドで数値データの概要を確認
df.describe()
Out[0]
PassengerId Survived Pclass Age SibSp Parch Fare
count 891.000000 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000
mean 446.000000 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208
std 257.353842 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429
min 1.000000 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000
25% 223.500000 0.000000 2.000000 20.125000 0.000000 0.000000 7.910400
50% 446.000000 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200
75% 668.500000 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000
max 891.000000 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200

記述統計の確認 (名義型)

質的変数の件数、ユニーク数、最頻出、重複数が確認出来ます。

df.describe(include=['O'])
Out[0]

Name Sex Ticket Cabin Embarked
count 891 891 891 204 889
unique 891 2 681 147 3
top Braund, Mr. Owen Harris male 347082 B96 B98 S
freq 1 577 7 4 644

最頻値や重複数がさくっと確認できるのはとても便利ですね。

パーセンタイルの確認

describeメソッドでもオプションに追加すれば確認出来ますが、パーセンタイルだけ確認することもquantileメソッドを使えば可能になります。

# パーセンタイルで数値データの詳細を確認
df.quantile([0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,0.95,0.99,1])
Out[0]
PassengerId Survived Pclass Age SibSp Parch Fare
0.00 1.0 0.0 1.0 0.42 0.0 0.0 0.00000
0.10 90.0 0.0 1.0 14.00 0.0 0.0 7.55000
0.20 179.0 0.0 1.0 19.00 0.0 0.0 7.85420
0.30 268.0 0.0 2.0 22.00 0.0 0.0 8.05000
0.40 357.0 0.0 2.0 25.00 0.0 0.0 10.50000
0.50 446.0 0.0 3.0 28.00 0.0 0.0 14.45420
0.60 535.0 0.0 3.0 31.80 0.0 0.0 21.67920
0.70 624.0 1.0 3.0 36.00 1.0 0.0 27.00000
0.80 713.0 1.0 3.0 41.00 1.0 1.0 39.68750
0.90 802.0 1.0 3.0 50.00 1.0 2.0 77.95830
0.95 846.5 1.0 3.0 56.00 3.0 2.0 112.07915
0.99 882.1 1.0 3.0 65.87 5.0 4.0 249.00622
1.00 891.0 1.0 3.0 80.00 8.0 6.0 512.32920

パーセンタイルからは数多くの情報やインサイトを得ることが出来ます。

  • PassengerIdは連番なのであまり利用の意味が無いかもしれません。
  • Survivedを確認すると70パーセンタイルで1になっているので、訓練データの乗客のうち約3割が生存者ということが分かります。
  • Pclassを確認すると、30%が1st、20%が2nd、そして残りの50%が3rdということが分かります。
  • Ageを確認すると、20%が19歳以下、70%が19歳〜50歳、残りの10%が50歳以上のようです。昔の平均年齢によりますが、高齢な乗客は訓練データにはあまり存在しないようです。
  • SibSpは約70%が0人、Parchも約80%が0人なようです。SibSpとParchを組み合わせた複合変数を作ってみたほうがいいかも知れませんが、もしかしたら1人で乗ってる人が多いのでしょうか?友達と一緒という可能性もありますね。もう少し時代背景などを勉強したら1人が多い理由がわかるかもしれません。例えばNewYorkに仕事を探しに行く人が多かったからなど (注:ただの仮説です。)
  • Fareにはかなりばらつきがあるようです。0~512のようです。(単位はドルかな?) 0ドルでの乗船は色々憶測を立てることが出来そうです。乳幼児は無料なのか、関係者は無料なのか、それともゴミデータなのかなど名前や年齢などと組み合わせて見る必要がありそうです。最高額で乗船した人が生存状況も気になります。Fareが高い部屋の近くはもしかしたら生存確率に関係あるかも知れませんね。(脱出用ボートがある非常階段から近い遠いなどはあるかも知れません。)

重複データの中身の確認

describeメソッドで最頻出の文字列を確認することが出来ますが、念のためもう少し詳細を確認しておきます。

# Sexの重複レコード件数の降順
df.groupby(["Sex"]).count().sort_values(by="PassengerId",ascending=False).head()
Out[0]

PassengerId Survived Pclass Name Age SibSp Parch Ticket Fare Cabin Embarked
Sex
male 577 577 577 577 453 577 577 577 577 107 577
female 314 314 314 314 261 314 314 314 314 97 312
# Ticketの重複レコード件数の降順
df.groupby(["Ticket"]).count().sort_values(by="PassengerId",ascending=False).head()
Out[0]

PassengerId Survived Pclass Name Sex Age SibSp Parch Fare Cabin Embarked
Ticket
1601 7 7 7 7 7 4 7 7 7 0 7
CA. 2343 7 7 7 7 7 0 7 7 7 0 7
347082 7 7 7 7 7 7 7 7 7 0 7
CA 2144 6 6 6 6 6 6 6 6 6 0 6
347088 6 6 6 6 6 6 6 6 6 0 6
# Cabinの重複レコード件数の降順
df.groupby(["Cabin"]).count().sort_values(by="PassengerId",ascending=False).head()
Out[0]

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Embarked
Cabin
C23 C25 C27 4 4 4 4 4 4 4 4 4 4 4
G6 4 4 4 4 4 4 4 4 4 4 4
B96 B98 4 4 4 4 4 4 4 4 4 4 4
F2 3 3 3 3 3 3 3 3 3 3 3
C22 C26 3 3 3 3 3 3 3 3 3 3 3
# Embarkedの重複レコード件数の降順
df.groupby(["Embarked"]).count().sort_values(by="PassengerId",ascending=False).head()
Out[0]

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin
Embarked
S 644 644 644 644 644 554 644 644 644 644 129
C 168 168 168 168 168 130 168 168 168 168 69
Q 77 77 77 77 77 28 77 77 77 77 4
スポンサーリンク

まとめ

まだデータ確認の前半部分ですが、かなりボリュームが多くなってしまいました。
実業務でもデータ確認や加工に大部分の時間をかけることが多いと思います。
後半も近いうちに投稿する予定です。

スポンサーリンク

参照

Group by: split-apply-combine — pandas 2.2.1 documentation
タイトルとURLをコピーしました