Sunday, November 2, 2025

Pandasで始める実践的データ分析の第一歩

現代において、データは新しい石油とも言われ、あらゆるビジネスや研究の中心に位置しています。このデータを有効に活用する能力は、現代のプロフェッショナルにとって不可欠なスキルとなりました。特にPythonは、その柔軟性と強力なエコシステムにより、データサイエンスの世界で最も主要な言語としての地位を確立しています。しかし、Pythonの標準ライブラリだけでは、複雑で大規模なデータセットを効率的に扱うことは困難です。ここで登場するのが、Pythonにおけるデータ分析の代名詞とも言えるライブラリ、Pandasです。

Pandasは単なるツールではありません。それは、構造化データを扱うための思考のフレームワークを提供します。ExcelのスプレッドシートやSQLのデータベーステーブルのように、直感的でありながら、プログラムによる自動化と高度な分析能力を兼ね備えています。この記事では、Pandasを初めて使う方から、基本は知っているけれどさらに深く理解したい方までを対象に、Pandasの核心的な概念から実践的なデータ操作、そしてデータ分析のワークフロー全体を、単なる機能の羅列ではなく、「なぜそうするのか」という本質に焦点を当てて解説していきます。データの読み込み、整形(クレンジング)、基本的な集計と分析まで、一歩一歩着実に進んでいきましょう。

第1章 Pandasの存在理由:なぜ私たちはPandasを選ぶのか?

データ分析の旅を始める前に、まず「なぜPandasなのか?」という根源的な問いに答えることが重要です。Pythonには標準でリストや辞書といったデータ構造がありますが、なぜそれらでは不十分なのでしょうか。また、数値計算に特化したライブラリとして有名なNumPyも存在します。Pandasがこれらのツールとどう異なり、どのような独自の価値を提供するのかを理解することが、効果的な学習の第一歩となります。

Python標準のリストと辞書との比較

Pythonのリストや辞書は非常に柔軟で強力ですが、表形式のデータを扱うにはいくつかの課題があります。例えば、複数の人物の年齢と都市のデータを考えてみましょう。


# Pythonのリストのリストでデータを表現
data_list = [
    ['Alice', 25, 'New York'],
    ['Bob', 30, 'Los Angeles'],
    ['Charlie', 35, 'Chicago']
]

# '都市'の列だけを取り出すのは少し面倒
cities = [row[2] for row in data_list]
print(cities)
# 出力: ['New York', 'Los Angeles', 'Chicago']

# 全員の年齢を1歳加算する
for row in data_list:
    row[1] += 1
print(data_list)
# 出力: [['Alice', 26, 'New York'], ['Bob', 31, 'Los Angeles'], ['Charlie', 36, 'Chicago']]

上記のように、特定の列へのアクセスや、列全体に対する一括操作が直感的ではありません。インデックス番号(この場合は `[2]` や `[1]`)を常に覚えておく必要があり、コードの可読性が低下し、エラーの原因にもなり得ます。Pandasは、この問題を「ラベル」付きのデータ構造を提供することで解決します。

数値計算の雄、NumPyとの関係

NumPyは、高速な数値計算を実現するためのPythonライブラリです。特に、`ndarray`という多次元配列オブジェクトは、同じデータ型の要素を密にメモリ上に配置することで、驚異的な計算速度を誇ります。Pandasは、このNumPyの配列を内部的に利用しており、そのパフォーマンスの恩恵を大きく受けています。

しかし、NumPyの`ndarray`にはいくつかの制約があります。

  1. データ型の制約: 一つの配列には、同じデータ型(例:整数のみ、浮動小数点数のみ)しか格納できません。しかし、実際のデータセットは、数値、文字列、日付などが混在していることがほとんどです。
  2. ラベルの欠如: NumPyの配列は、0から始まる整数インデックスでしか要素にアクセスできません。`data[0, 2]` のようなアクセス方法は、そのインデックスが何を意味するのか(0番目の行の、2番目の列)がコード上から自明ではありません。

Pandasは、これらのNumPyの制約を克服するために生まれました。NumPyの高速な計算基盤の上に、柔軟なデータ型直感的なラベル(インデックス名やカラム名)を導入したのです。これにより、人間が理解しやすい形で、かつコンピュータが効率的に処理できる形でデータを扱えるようになりました。これが、データ分析においてPandasがデファクトスタンダードとなった最大の理由です。

第2章 Pandasの心臓部:SeriesとDataFrame

Pandasの世界は、主に二つのデータ構造、Series(シリーズ)DataFrame(データフレーム)によって構成されています。これらを深く理解することが、Pandasを自在に操るための鍵となります。

Series:1次元のラベル付き配列

Seriesは、1次元の配列に似たオブジェクトですが、各要素がインデックスと呼ばれるラベルと対応している点が特徴です。インデックスは、デフォルトでは0から始まる整数ですが、任意の文字列などを指定することも可能です。

これは、Pythonの辞書とNumPyの1次元配列を組み合わせたようなものと考えることができます。

Index | Data -------|------- 'a' | 100 'b' | 200 'c' | 300 'd' | 400


import pandas as pd

# リストからSeriesを作成
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
print(s)

出力結果:


a    10
b    20
c    30
d    40
dtype: int64

このように、データ (`[10, 20, 30, 40]`) と、それに対応するインデックス (`['a', 'b', 'c', 'd']`) が紐付いています。これにより、`s['b']` のようにラベル名でデータにアクセスしたり、`s[s > 25]` のようにデータに基づいたフィルタリング(ブールインデックス参照)を直感的に行ったりできます。

DataFrame:2次元のラベル付きデータ構造

DataFrameは、Pandasで最もよく使われる、2次元のテーブル形式のデータ構造です。ExcelのスプレッドシートやSQLのテーブルを想像すると分かりやすいでしょう。DataFrameは、同じインデックスを共有するSeriesの集まりと考えることもできます。

DataFrameは、行のラベルであるインデックス(index)と、列のラベルであるカラム(columns)を持っています。

| Column A | Column B ---------|----------|---------- Index 1 | Value1A | Value1B Index 2 | Value2A | Value2B Index 3 | Value3A | Value3B

Pythonの辞書を使ってDataFrameを簡単に作成できます。辞書のキーがカラム名に、値(リスト形式)が各列のデータになります。


import pandas as pd

# 辞書からDataFrameを作成
data = {
    'Name': ['Alice', 'Bob', 'Charlie'],
    'Age': [25, 30, 35],
    'City': ['New York', 'Los Angeles', 'Chicago']
}
df = pd.DataFrame(data)

print(df)

出力結果:


      Name  Age         City
0    Alice   25     New York
1      Bob   30  Los Angeles
2  Charlie   35      Chicago

この`df`というオブジェクトが、私たちのデータ分析の主戦場となります。特定の列(Series)を `df['Age']` のようにして取り出したり、特定の行をインデックスで指定したり、さらには複数の列と行を組み合わせてデータをスライスしたりと、多彩な操作が可能です。DataFrameを理解することは、Pandasを理解することそのものと言っても過言ではありません。

第3章 データの読み込み:冒険の始まり

データ分析の最初のステップは、分析対象のデータをプログラムに読み込むことです。データはCSVファイル、Excelファイル、データベースなど、様々な形式で存在します。Pandasはこれらの多様なデータソースに対応する強力な読み込み機能を提供しており、中でもCSVファイルを読み込むための `pd.read_csv()` は最も頻繁に使用される関数の一つです。

単純にファイルパスを指定するだけでもデータを読み込めますが、実世界のデータはしばしば不完全であったり、特殊なフォーマットであったりします。`read_csv` の強力なオプションを理解することで、これらの課題にスマートに対応できます。

ここでは、以下のような内容のCSVファイル `users.csv` があると仮定します。


user_id;name;age;city;registered_date
1;Alice;25;New York;2022-01-15
2;Bob;30;Los Angeles;2021-11-20
3;Charlie;35;Chicago;2022-03-10

このファイルには、区切り文字がカンマ(,)ではなくセミコロン(;)であるという特徴があります。


import pandas as pd
import io

# サンプルCSVデータを作成
csv_data = """user_id;name;age;city;registered_date
1;Alice;25;New York;2022-01-15
2;Bob;30;Los Angeles;2021-11-20
3;Charlie;35;Chicago;2022-03-10
"""

# 通常、ファイルパスを渡すが、ここでは文字列から読み込む
# df = pd.read_csv('users.csv') 

# 区切り文字を指定して読み込む
df = pd.read_csv(io.StringIO(csv_data), sep=';')

print(df.head()) # .head()は最初の5行を表示するメソッド

出力結果:


   user_id     name  age         city registered_date
0        1    Alice   25     New York      2022-01-15
1        2      Bob   30  Los Angeles      2021-11-20
2        3  Charlie   35      Chicago      2022-03-10

`read_csv` の重要な引数たち

`read_csv`を真に使いこなすためには、以下の引数を覚えておくと非常に役立ちます。

  • `sep` (または `delimiter`): データの区切り文字を指定します。デフォルトはカンマ `,` ですが、タブ `\t` や上記のセミコロン `;` など、ファイルに合わせて変更します。
  • `header`: ヘッダー(カラム名)として使用する行の番号を指定します。デフォルトは `0`(最初の行)です。ヘッダーがない場合は `None` を指定します。
  • `index_col`: インデックスとして使用する列を指定します。列名または列番号で指定できます。例えば、`index_col='user_id'` とすれば、user_idがDataFrameのインデックスになります。
  • `usecols`: 読み込む列をリストで指定します。`['name', 'age']` のように指定すれば、メモリを節約し、不要なデータを最初から除外できます。
  • `parse_dates`: 日付として解釈したい列をリストで指定します。`['registered_date']` のように指定すると、Pandasが自動的に日付型(datetime)に変換しようと試みます。これは後続の時系列分析で極めて重要です。
  • `encoding`: ファイルの文字エンコーディングを指定します。日本語のデータを含むCSVファイル(特にWindowsで作成されたもの)は `Shift_JIS` (`'cp932'`) であることが多く、これを正しく指定しないと文字化け (`UnicodeDecodeError`) が発生します。世界標準は `'utf-8'` です。
  • `dtype`: 列ごとにデータ型を明示的に指定します。例えば、`{'user_id': str, 'age': int}` のように辞書で渡します。これにより、Pandasの自動型推論による意図しない型変換(例:先頭が0で始まるIDが数値として読み込まれ、0が消えてしまう問題)を防ぐことができます。

これらの引数を適切に使い分けることで、データ読み込みの段階で多くの前処理を済ませることができ、後の分析工程をスムーズに進めることができます。データ分析は、しばしば「Garbage In, Garbage Out(ゴミを入れれば、ゴミしか出てこない)」と言われます。正確なデータ読み込みは、質の高い分析を行うための、最も重要で基本的なステップなのです。

第4章 データクレンジング:混沌から秩序へ

現実世界のデータは、決して綺麗ではありません。欠損値、重複、データ型の不一致など、様々な「ノイズ」が含まれています。これらのノイズを放置したまま分析を進めると、誤った結論を導き出してしまう可能性があります。データクレンジング(またはデータ前処理)は、データの中からこれらのノイズを取り除き、分析に適した形に整える、地道ですが極めて重要なプロセスです。

4.1 欠損値(Missing Values)との対峙

欠損値は、データが収集されなかった、入力されなかったなどの理由で発生します。Pandasでは、これらは `NaN` (Not a Number) という特別な値で表現されます。

欠損値の発見

まずは、データセットのどこに、どれくらいの欠損値があるのかを把握する必要があります。`isnull()` (または `isna()`) メソッドは、各要素が欠損値であれば `True`、そうでなければ `False` を返すDataFrameを生成します。これに `sum()` メソッドを組み合わせることで、各列の欠損値の数を簡単に集計できます。


import pandas as pd
import numpy as np

data = {
    'A': [1, 2, np.nan, 4, 5],
    'B': [10, np.nan, np.nan, 40, 50],
    'C': ['apple', 'banana', 'cherry', 'date', np.nan]
}
df = pd.DataFrame(data)

print("元のDataFrame:")
print(df)
print("\n各列の欠損値の数:")
print(df.isnull().sum())

出力結果:


元のDataFrame:
     A     B       C
0  1.0  10.0   apple
1  2.0   NaN  banana
2  NaN   NaN  cherry
3  4.0  40.0    date
4  5.0  50.0     NaN

各列の欠損値の数:
A    1
B    2
C    1
dtype: int64

欠損値の処理戦略

欠損値を発見したら、次はいかにして対処するかを決めなければなりません。主な戦略は「削除」と「補完」の二つです。

1. 削除 (`dropna`)

`dropna()` メソッドは、欠損値を含む行または列を削除します。最もシンプルですが、注意が必要です。貴重な情報を失ってしまう可能性があるため、安易な使用は避けるべきです。

  • `df.dropna()`: 欠損値が一つでも含まれるを削除します。
  • `df.dropna(axis=1)`: 欠損値が一つでも含まれるを削除します。
  • `df.dropna(how='all')`: 全ての要素が欠損値である行のみを削除します。
  • `df.dropna(thresh=2)`: 欠損値でない値が2つ未満の行を削除します。

どの行を削除するかは、そのデータが分析全体に与える影響を考慮して慎重に判断する必要があります。例えば、欠損率が非常に高い行や、分析の根幹に関わらない特徴量の欠損であれば、削除が妥当な場合もあります。

2. 補完 (`fillna`)

データを削除する代わりに、何らかの「妥当な」値で欠損値を埋めるのが補完です。どの値で埋めるかは、データの性質や文脈に大きく依存します。

  • 定数で補完: `df.fillna(0)` のように、特定の値(0や'Unknown'など)で埋めます。最も単純ですが、データの分布を歪める可能性があります。
  • 統計値で補完: 数値データの場合、列の平均値 (`df['A'].mean()`) や中央値 (`df['A'].median()`) で補完するのは一般的な手法です。外れ値の影響を受けにくい中央値の方が、より頑健な選択肢となることが多いです。
  • 
    # 列'A'の欠損値を列'A'の平均値で補完
    mean_A = df['A'].mean()
    df['A'].fillna(mean_A, inplace=True) # inplace=Trueは元のDataFrameを直接変更する
    print(df)
    
  • 前方/後方補完: `df.fillna(method='ffill')`(前方補完)は直前の値で、`df.fillna(method='bfill')`(後方補完)は直後の値で欠損値を埋めます。これは時系列データなどで特に有効です。

欠損値の処理には唯一の正解はありません。データの背景を理解し、どの手法が最も分析の目的を歪めないかを考える、分析者の洞察力が試される場面です。

4.2 重複データとの戦い

データセットには、全く同じ内容の行が複数含まれていることがあります。これは入力ミスやシステムの不具合で発生し、集計結果を水増しするなど、分析に悪影響を与えます。`duplicated()` と `drop_duplicates()` を使って対処します。

  • `df.duplicated()`: 各行が重複しているかどうかを判定し、ブール値のSeriesを返します(最初に出現した行は `False`、それ以降の重複行は `True`)。
  • `df.drop_duplicates()`: 重複した行を削除した新しいDataFrameを返します。
    • `keep` 引数でどの重複行を残すか指定できます(`'first'` (デフォルト), `'last'`, `False` (全て削除))。
    • `subset` 引数に列名のリストを渡すことで、特定の列の組み合わせにおいて重複している行のみを対象にできます。

data = {
    'ID': [1, 2, 3, 1],
    'Name': ['Alice', 'Bob', 'Charlie', 'Alice']
}
df_dup = pd.DataFrame(data)

print("元のDataFrame:")
print(df_dup)

# 重複行を削除
df_no_dup = df_dup.drop_duplicates()
print("\n重複削除後のDataFrame:")
print(df_no_dup)

4.3 データ型の変換という儀式

データは、見た目と内部的な表現(データ型, dtype)が異なっていることがあります。例えば、数値であるべき列が文字列(object型)として読み込まれていたり、日付がただの文字列だったりします。正しいデータ型に変換しないと、計算やソートが正しく行えません。

`astype()` による型変換

`astype()` メソッドは、列のデータ型を変換するための最も基本的な方法です。


data = {
    'age_str': ['25', '30', '35'],
    'price_str': ['1,500', '2,000', '1,200']
}
df_types = pd.DataFrame(data)
print("変換前のデータ型:")
print(df_types.dtypes)

# 'age_str'を整数型(int)に変換
df_types['age_int'] = df_types['age_str'].astype(int)

# 'price_str'のカンマを削除してから整数型に変換
df_types['price_int'] = df_types['price_str'].str.replace(',', '').astype(int)

print("\n変換後のデータ型:")
print(df_types.dtypes)
print("\n変換後のDataFrame:")
print(df_types)

日付型への変換:`pd.to_datetime()`

日付や時刻を扱う上で、それらを文字列として保持しておくのは非常に不便です。Pandasの `datetime` 型に変換することで、年や月、曜日を抽出したり、日付間の差を計算したりと、時系列分析特有の強力な機能が使えるようになります。

`pd.to_datetime()` 関数は、様々な形式の文字列を賢く解釈して `datetime` 型に変換してくれます。


date_str_series = pd.Series(['2023-01-01', '2023/01/02', 'Jan 03, 2023'])
date_dt_series = pd.to_datetime(date_str_series)

print(date_dt_series)
print("\nデータ型:", date_dt_series.dtype)

# 年や曜日を簡単に抽出できる
print("\n年:", date_dt_series.dt.year)
print("曜日:", date_dt_series.dt.day_name())

データクレンジングは、派手さはありませんが、分析の土台を固めるための不可欠な工程です。この工程を丁寧に行うことが、信頼性の高い分析結果への近道となります。

第5章 データの選択とフィルタリング:宝探し

データ全体を眺めるだけでは、深い洞察は得られません。分析とは、多くの場合、特定の条件に合致するデータの一部を切り出し(スライス)、その部分集合の性質を調べることです。Pandasは、この「データの切り出し」操作のために、非常に強力で、時に少し紛らわしいいくつかの方法を提供しています。その中でも、`.loc` と `.iloc`、そしてブールインデックス参照は必ずマスターすべき三種の神器です。

`.loc` vs `.iloc`:ラベルか、位置か

この二つのアクセサは、初心者が最も混同しやすいポイントですが、その違いは明確です。「`.loc`はラベル(label)に基づき、`.iloc`は整数の位置(integer location)に基づく」と覚えましょう。

name age city idx_a Alice 25 New York idx_b Bob 30 LA idx_c Charlie 35 Chicago .loc['idx_b', 'age'] -> 30 (ラベルで指定) .iloc[1, 1] -> 30 (位置で指定)

`.loc`:ラベルベースの選択

`.loc`は、行のインデックス名と列のカラム名を使ってデータを選択します。


import pandas as pd

data = {
    'Name': ['Alice', 'Bob', 'Charlie', 'David'],
    'Age': [25, 30, 35, 40],
    'City': ['New York', 'Los Angeles', 'Chicago', 'Houston']
}
df = pd.DataFrame(data, index=['a', 'b', 'c', 'd'])

# 単一の要素を選択 (行'b', 列'Age')
print(df.loc['b', 'Age'])
#=> 30

# 1行を丸ごと選択 (行'c')
print(df.loc['c'])

# 複数行、複数列を選択 (スライス)
# 注意: .locのスライスは終了値('c')も含まれる!
print(df.loc['b':'c', ['Name', 'City']])

`.iloc`:整数位置ベースの選択

`.iloc`は、Pythonのリストのスライスと同様に、0から始まる整数のインデックスを使ってデータを選択します。


# 単一の要素を選択 (1行目, 2列目)
print(df.iloc[1, 2])
#=> 'Los Angeles'

# 1行を丸ごと選択 (0行目)
print(df.iloc[0])

# 複数行、複数列を選択 (スライス)
# 注意: .ilocのスライスは終了値(3)は含まれない!
print(df.iloc[0:3, 0:2])

`.loc`と`.iloc`を使い分けることで、意図が明確で間違いの少ないコードを書くことができます。例えば、データがソートされて行の順序が変わる可能性がある場合でも、`.loc`を使えばインデックス名で確実に特定の行を捉えることができます。

ブールインデックス参照:最強のフィルタリング手法

「30歳以上のユーザーを抽出する」「シカゴ在住のユーザーのデータだけを見る」といった、条件に基づいたフィルタリングはデータ分析で最も頻繁に行われる操作です。これを実現するのがブールインデックス参照です。

この手法は2つのステップで動作します。

  1. 条件式の評価: DataFrameの列に対して比較演算子 (`>`, `<`, `==`など) を使って条件式を立てます。これにより、各行が条件を満たすかどうかの `True`/`False` からなるブール値のSeriesが生成されます。
  2. フィルタリング: 生成されたブール値のSeriesをDataFrameの `[]` に渡します。これにより、`True` に対応する行だけが抽出されます。

# ステップ1: 条件式の評価
condition = df['Age'] >= 35
print("条件式の評価結果 (ブール値のSeries):")
print(condition)

# ステップ2: フィルタリング
print("\n35歳以上のユーザー:")
print(df[condition])

出力結果:


条件式の評価結果 (ブール値のSeries):
a    False
b    False
c     True
d     True
Name: Age, dtype: bool

35歳以上のユーザー:
      Name  Age     City
c  Charlie   35  Chicago
d    David   40  Houston

複数の条件を組み合わせる

複数の条件を組み合わせるには、論理演算子 `&` (AND), `|` (OR), `~` (NOT) を使用します。このとき、各条件式を必ず丸括弧 `()` で囲む必要があることに注意してください。これは演算子の優先順位の問題を避けるためです。


# 30歳以上 かつ シカゴ在住 のユーザー
condition_multi = (df['Age'] >= 30) & (df['City'] == 'Chicago')
print(df[condition_multi])

# ニューヨーク在住 または 40歳 のユーザー
condition_or = (df['City'] == 'New York') | (df['Age'] == 40)
print(df[condition_or])

ブールインデックス参照は、SQLの `WHERE` 句に相当する強力な機能です。これを使いこなすことができれば、複雑な条件でデータを抽出し、分析の精度を格段に向上させることができます。

第6章 基本的な統計分析:データとの対話

データを綺麗に整え、自在に抽出できるようになったら、いよいよデータから意味のある情報を引き出す段階、つまり分析に入ります。Pandasは、記述統計(データを要約し、その特徴を記述する統計)のための豊富な機能を提供しています。これらは、データセット全体の概要を素早く掴んだり、特定のグループ間の違いを比較したりするための第一歩となります。

`.describe()`:データセットの健康診断

まず最初に試すべきは `describe()` メソッドです。これは、数値列に関する主要な記述統計量を一度に算出し、データセットの全体像を素早く把握するための強力なツールです。


import pandas as pd
import numpy as np

data = {
    'age': [22, 25, 31, 45, 52, 28, 33, 39],
    'salary': [50000, 55000, 70000, 120000, 150000, 62000, 80000, 95000],
    'gender': ['M', 'F', 'F', 'M', 'M', 'F', 'M', 'F']
}
df = pd.DataFrame(data)

print(df.describe())

出力結果:


             age         salary
count   8.000000       8.000000
mean   34.375000   85250.000000
std     9.938475   36004.629119
min    22.000000   50000.000000
25%    27.250000   59750.000000
50%    32.000000   75000.000000
75%    40.500000  101250.000000
max    52.000000  150000.000000

この出力から何が読み取れるでしょうか?

  • `count`: データ(非欠損値)の数。
  • `mean`: 平均値。データの中心的な傾向を示します。
  • `std`: 標準偏差。データのばらつき具合を示します。値が大きいほど、データが平均から広く散らばっていることを意味します。
  • `min`, `max`: 最小値と最大値。データの範囲を把握し、異常な値(外れ値)の存在を示唆することがあります。
  • `25%`, `50%`, `75%`: 四分位数。データを小さい順に並べたときの、25%地点、50%地点(中央値、median)、75%地点の値です。平均値が外れ値に影響されやすいのに対し、中央値はより頑健な中心傾向の指標となります。`mean`と`50%(median)`が大きく乖離している場合、データが歪んでいる(一部に極端に大きい/小さい値がある)可能性があります。

`describe()`は、データ分析の初期段階で必ず実行し、データの分布やスケール感を頭に入れておくべき、まさに「健康診断」のようなメソッドです。

個別の集計関数

もちろん、`mean()`、`median()`、`sum()`、`std()`、`var()`(分散)、`count()`、`min()`、`max()`など、個別の統計量を計算するメソッドも用意されています。


print("平均年齢:", df['age'].mean())
print("給与の中央値:", df['salary'].median())
print("給与の合計:", df['salary'].sum())

カテゴリデータの要約:`.value_counts()`

数値データだけでなく、性別や製品カテゴリのようなカテゴリカルデータの分布を理解することも重要です。`.value_counts()` は、列に含まれる各値の出現回数を集計する、非常に便利なメソッドです。


print(df['gender'].value_counts())

# 割合で表示する場合
print(df['gender'].value_counts(normalize=True))

出力結果:


M    4
F    4
Name: gender, dtype: int64

M    0.5
F    0.5
Name: gender, dtype: float64

これにより、データセット内の男女比が均等であることが一目でわかります。

`groupby()`:データ分析の真髄

データ分析の力の源泉は、データを特定のカテゴリでグループ化し、各グループごとに統計量を計算することにあります。例えば、「性別ごとに平均給与を比較したい」「都市ごとに売上の合計を知りたい」といった要求は、`groupby()` を使って実現します。

`groupby()` の操作は、Split-Apply-Combine という3つのステップで考えると理解しやすくなります。

  1. Split(分割): 指定されたキー(例:'gender'列)に基づいて、DataFrameをサブグループに分割します。
  2. Apply(適用): 各サブグループに対して、集計関数(例:`mean()`, `sum()`)を適用します。
  3. Combine(結合): 適用結果を新しいデータ構造(DataFrameやSeries)に結合して返します。

[DataFrame] --Split by 'gender'--> [Group M] + [Group F] | | | | Apply mean() Apply mean() | | | +-----Combine-----<-- [Result for M] + [Result for F]


# 性別ごとにグループ化し、各グループの平均値を計算
gender_mean = df.groupby('gender').mean()
print(gender_mean)

# 性別ごとにグループ化し、給与(salary)の記述統計量を表示
salary_stats_by_gender = df.groupby('gender')['salary'].describe()
print(salary_stats_by_gender)

出力結果:


             age     salary
gender                     
F      33.750000   74250.0
M      35.000000   96250.0

        count      mean           std      min      25%     50%       75%       max
gender                                                                            
F         4.0   74250.0  14545.899852  55000.0  60250.0  71000.0   83750.0   95000.0
M         4.0   96250.0  44280.082049  50000.0  65000.0  97500.0  128750.0  150000.0

この結果から、このデータセットにおいては、男性の方が平均年齢も平均給与もわずかに高いことが分かります。さらに給与の統計量を見ると、男性の給与の標準偏差(std)が女性に比べて非常に大きく、ばらつきが大きい(高給与の人とそうでない人の差が激しい)ことが示唆されます。

`groupby()` は、Pandasにおける最も強力で表現力豊かな機能の一つです。これを使いこなすことで、データに潜むパターンや関係性を明らかにすることができます。

第7章 実践的なヒントと次のステップ

ここまでで、Pandasを使ったデータ分析の基本的なワークフローを学びました。最後に、より効率的で洗練されたコードを書くためのヒントと、ここからさらに知識を広げていくための道筋を示します。

メソッドチェーン:流れるようなデータ操作

Pandasの多くのメソッドは、新しいDataFrameやSeriesを返すように設計されています。この性質を利用して、複数の操作をドット(`.`)で繋げて一連の処理として記述することができます。これをメソッドチェーンと呼びます。

例えば、「30歳以上のユーザーを抽出し、都市ごとにグループ化し、平均給与を計算して、給与の高い順に並べ替える」という一連の処理を考えてみましょう。

メソッドチェーンを使わない場合:


df_over30 = df[df['age'] >= 30]
grouped = df_over30.groupby('city')
mean_salary = grouped['salary'].mean()
sorted_salary = mean_salary.sort_values(ascending=False)

メソッドチェーンを使う場合:


# サンプルデータを再定義
data = {
    'age': [22, 25, 31, 45, 52, 28, 33, 39],
    'salary': [50000, 55000, 70000, 120000, 150000, 62000, 80000, 95000],
    'city': ['Tokyo', 'Osaka', 'Tokyo', 'Osaka', 'Fukuoka', 'Tokyo', 'Osaka', 'Fukuoka']
}
df = pd.DataFrame(data)

# メソッドチェーンによる記述
result = (
    df[df['age'] >= 30]
    .groupby('city')['salary']
    .mean()
    .sort_values(ascending=False)
)
print(result)

メソッドチェーンを使うと、中間変数を生成する必要がなくなり、コードが上から下へと一直線に読めるため、処理の流れが非常に分かりやすくなります。長いチェーンになる場合は、上記のように括弧で囲み、各メソッドを改行して記述すると可読性がさらに向上します。

パフォーマンスに関する考察:ベクトル化の力

Pandasの操作に慣れてくると、Pythonの `for` ループを使って行ごとに処理を書きたくなるかもしれません。しかし、これはPandasのパフォーマンスを著しく低下させるアンチパターンです。Pandasは内部的にNumPyを利用しており、列全体に対する操作(ベクトル化された操作)をC言語レベルで高速に実行します。

例えば、全ての従業員の給与を5%上げる処理を考えます。

非推奨な方法 (`for`ループ):


new_salaries = []
for salary in df['salary']:
    new_salaries.append(salary * 1.05)
df['new_salary_loop'] = new_salaries

推奨される方法 (ベクトル化):


df['new_salary_vector'] = df['salary'] * 1.05

データ量が少ないうちは差は感じられないかもしれませんが、データが数万、数百万行になると、両者の実行速度には桁違いの差が生まれます。可能な限り `for` ループを避け、Pandasが提供する組み込み関数や演算子を使って列全体を一度に操作する「ベクトル化」の発想を常に持つことが、効率的なデータ分析コードを書くための秘訣です。

次のステップへ

この記事では、Pandasの基本的ながらも非常に強力な機能群を巡る旅をしてきました。しかし、Pandas、そしてデータ分析の世界はさらに奥深く、広がっています。

  • データ可視化 (Data Visualization): 数値の羅列だけでは分からないデータのパターンや傾向を、グラフを使って直感的に理解する技術です。Pandasは `plot()` メソッドで基本的なグラフを描画できますが、より高度で美しい可視化のためには、MatplotlibSeabornといったライブラリと組み合わせて使うのが一般的です。
  • 高度なデータ操作: 複数のDataFrameを結合する `merge` や `join`、ピボットテーブルを作成する `pivot_table`、時系列データを扱うための高度な機能など、Pandasにはまだ探求すべき多くの機能があります。
  • 機械学習 (Machine Learning): データクレンジングと前処理は、機械学習モデルを構築するための準備段階でもあります。Pandasで整形したデータを、Scikit-learnのような機械学習ライブラリに渡すことで、未来の予測や分類といった、さらに高度な分析へと進むことができます。

おわりに

Pandasは、混沌とした生データに秩序を与え、そこから価値ある洞察を引き出すための羅針盤です。本記事で紹介した概念とテクニックは、その広大な海を航海するための第一歩に過ぎません。DataFrameという強力な船を操り、`groupby` やブールインデックス参照といった航海術を駆使することで、これまで見えなかったデータの新大陸を発見することができるでしょう。

最も重要なのは、実際に自分の手でデータを触ってみることです。公開されているデータセットを探し、この記事で学んだことを試しながら、自分なりの問いをデータに投げかけてみてください。試行錯誤の過程こそが、データ分析家としての最も優れた成長の糧となるはずです。あなたのデータ分析の冒険が、ここから始まることを願っています。


0 개의 댓글:

Post a Comment