Pandasの使い方まとめ|PythonでCSV操作・欠損値処理・集計・可視化

プログラミング

データ分析と操作のための強力なツール

はじめに:Pandasとは?

Pandasは、Pythonプログラミング言語で使用される、強力で柔軟性の高いオープンソースのデータ分析・操作ライブラリです。2008年にWes McKinneyによって開発が開始され、以来、データサイエンスコミュニティで広く利用されています。特に、表形式データ(ExcelのスプレッドシートやSQLのテーブルのような構造化データ)を扱うのに非常に優れており、データの前処理、クリーニング、分析、可視化といった一連のデータサイエンスワークフローで中心的な役割を果たします。

Pandasの名前は、「Panel Data」という計量経済学の用語に由来します。NumPyを基盤として構築されており、NumPyの高速な数値計算能力を継承しつつ、より柔軟で直感的なデータ操作機能を提供します。

機械学習プロジェクトにおいては、データの準備(前処理)が非常に重要であり、多くの時間を要する工程ですが、Pandasを活用することでこの工程を大幅に効率化できます。

Pandasの主要なデータ構造

Pandasには主に2つのデータ構造があります。これらを理解することがPandasを使いこなす第一歩です。

1. Series (シリーズ)

Seriesは、1次元のラベル付き配列です。NumPyの1次元配列に似ていますが、各要素に対応するラベル(インデックス)を持つ点が特徴です。インデックスはデフォルトで0から始まる整数が割り当てられますが、任意のラベル(文字列など)を指定することも可能です。Seriesは、整数、浮動小数点数、文字列、Pythonオブジェクトなど、様々なデータ型を保持できますが、通常は同じ型の要素で構成されます。


import pandas as pd

# リストからSeriesを作成
data = [10, 20, 30, 40, 50]
s = pd.Series(data)
print(s)

# インデックスを指定してSeriesを作成
labels = ['a', 'b', 'c', 'd', 'e']
s_labeled = pd.Series(data, index=labels)
print(s_labeled)
            

Seriesはサイズ(要素数)が不変(immutable)ですが、値は変更可能(mutable)です。

2. DataFrame (データフレーム)

DataFrameは、2次元のラベル付きデータ構造で、異なる型の列を持つことができます。ExcelのスプレッドシートやSQLのテーブル、あるいはSeriesオブジェクトの辞書のようなものと考えることができます。行と列の両方にラベル(インデックスとカラム名)を持ちます。Pandasで最もよく使われるデータ構造です。

DataFrameは、値もサイズも変更可能(mutable)です。


import pandas as pd

# 辞書からDataFrameを作成
data = {
    '名前': ['田中', '佐藤', '鈴木'],
    '年齢': [25, 30, 22],
    '都市': ['東京', '大阪', '福岡']
}
df = pd.DataFrame(data)
print(df)

# インデックスを指定
df_indexed = pd.DataFrame(data, index=['ID1', 'ID2', 'ID3'])
print(df_indexed)
            

DataFrameは、データ分析の中核となるオブジェクトであり、多くの操作はこのDataFrameに対して行われます。

Panel (パネル) ※非推奨

以前は3次元のデータ構造であるPanelも提供されていましたが、現在は非推奨となり、MultiIndex(階層型インデックス)を持つDataFrameや、xarrayライブラリなどを使用することが推奨されています。

データの読み込みと書き出し 💾

Pandasは、様々な形式のファイルからデータを読み込んだり、処理結果をファイルに書き出したりするための強力な機能を提供しています。これにより、実世界の多様なデータソースを扱うことができます。

ファイル形式 読み込み関数 書き出しメソッド 備考
CSV (Comma-Separated Values) pd.read_csv() df.to_csv() 最も一般的に使用される形式の一つ。区切り文字の指定なども可能。
Excel (.xlsx, .xls) pd.read_excel() df.to_excel() openpyxlxlrd などの追加ライブラリが必要な場合があります。シート名の指定も可能。
JSON (JavaScript Object Notation) pd.read_json() df.to_json() Web APIなどでよく使われる形式。
SQL (データベース) pd.read_sql(), pd.read_sql_query(), pd.read_sql_table() df.to_sql() SQLAlchemy などのライブラリと連携して使用。
HTML pd.read_html() df.to_html() Webページ上のテーブルデータを抽出するのに便利。
Pickle (Pythonオブジェクトシリアライズ) pd.read_pickle() df.to_pickle() DataFrameオブジェクトをそのままの形で高速に保存・読み込み可能。
その他 HDF5, Parquet, Feather, Stata, SAS など多数対応 特定の用途や大規模データに適したフォーマット。

CSVファイルの読み込み例


import pandas as pd

# 'data.csv' という名前のCSVファイルを読み込む
# ファイルが存在しない場合はエラーになります
try:
    df_csv = pd.read_csv('data.csv')
    print("CSVファイルの読み込みに成功しました。")
    # 最初の5行を表示
    print(df_csv.head())
except FileNotFoundError:
    print("エラー: 'data.csv' が見つかりません。")

# DataFrameを 'output.csv' という名前で保存する (インデックスは保存しない)
# df_csvが正常に読み込めた場合のみ実行
if 'df_csv' in locals():
    try:
        df_csv.to_csv('output.csv', index=False)
        print("DataFrameを 'output.csv' に書き出しました。")
    except Exception as e:
        print(f"CSVファイルへの書き出し中にエラーが発生しました: {e}")
            

読み込み/書き出し時には、エンコーディング(例: encoding='utf-8', encoding='cp932' (Shift-JIS))、ヘッダーの有無、インデックスの扱いなど、多くのオプションを指定できます。

データ操作の基本 🔧

Pandasの真価は、読み込んだデータを柔軟に操作できる点にあります。ここでは基本的な操作をいくつか紹介します。

データの確認

  • df.head(n): 最初のn行を表示 (デフォルトは5行)
  • df.tail(n): 最後のn行を表示 (デフォルトは5行)
  • df.shape: DataFrameの形状(行数, 列数)をタプルで返す
  • df.info(): インデックス、列名、非null値の数、データ型、メモリ使用量などの要約情報を表示
  • df.describe(): 数値列に関する基本的な統計量(個数、平均、標準偏差、最小値、四分位数、最大値)を計算
  • df.dtypes: 各列のデータ型を表示
  • df.columns: 列名の一覧を取得
  • df.index: インデックス(行ラベル)の一覧を取得

データの選択・抽出 (Slicing & Indexing)

特定のデータを取り出す方法はいくつかあります。

  • 列の選択:
    • df['列名']: 1つの列をSeriesとして選択
    • df[['列名1', '列名2']]: 複数の列をDataFrameとして選択
  • 行の選択 (ラベルベース): .loc[]
    • df.loc['行ラベル']: 指定したラベルの行を選択
    • df.loc['開始ラベル':'終了ラベル']: ラベルで範囲指定(終了ラベルも含む)
    • df.loc[['ラベル1', 'ラベル2'], ['列名A', '列名B']]: 特定の行と列をラベルで指定
  • 行の選択 (整数位置ベース): .iloc[]
    • df.iloc[行番号]: 指定した番号(0から始まる)の行を選択
    • df.iloc[開始番号:終了番号]: 整数位置で範囲指定(終了番号は含まない)
    • df.iloc[[番号1, 番号2], [列番号A, 列番号B]]: 特定の行と列を整数位置で指定
  • 条件に基づく選択 (Boolean Indexing):
    • df[df['列名'] > 値]: 条件式を満たす行のみを抽出
    • df[(df['列A'] > 値1) & (df['列B'] == '値2')]: 複数の条件を組み合わせる(&はAND, |はOR)

import pandas as pd
import numpy as np

# サンプルDataFrame作成
data = {
    '製品': ['A', 'B', 'C', 'A', 'B', 'C'],
    '地域': ['東', '西', '東', '西', '東', '西'],
    '売上': [100, 150, 200, 120, 180, 250],
    '在庫': [30, 45, 50, 25, 55, 60]
}
df = pd.DataFrame(data, index=['日1', '日1', '日2', '日2', '日3', '日3'])

# 列を選択
print("--- 売上列 ---")
print(df['売上'])

# locを使ってラベルで選択
print("\n--- locによる選択 (日2の行) ---")
print(df.loc['日2'])

# ilocを使って整数位置で選択
print("\n--- ilocによる選択 (最初の2行) ---")
print(df.iloc[0:2]) # 0行目と1行目

# 条件で選択 (売上が150より大きい)
print("\n--- 条件による選択 (売上 > 150) ---")
print(df[df['売上'] > 150])
            

データの追加・削除

  • 列の追加: df['新しい列名'] = 値 or Series or 配列
  • 行の追加: pd.concat([df1, df2]) を使うのが一般的 (以前のappendメソッドは非推奨)
  • 列の削除: df.drop('列名', axis=1) または del df['列名']
  • 行の削除: df.drop('行ラベル', axis=0)

drop メソッドはデフォルトでは元のDataFrameを変更しません。変更を反映させたい場合は inplace=True を指定するか、結果を新しい変数に代入します。

欠損値の処理

実際のデータには欠損値(NaN: Not a Number)が含まれることがよくあります。Pandasはこれらを扱うための便利な機能を提供します。

  • df.isnull() / df.isna(): 各要素が欠損値かどうかを判定 (True/FalseのDataFrameを返す)
  • df.notnull() / df.notna(): 各要素が欠損値でないかどうかを判定
  • df.dropna(): 欠損値を含む行または列を削除
  • df.fillna(値): 欠損値を指定した値(例: 0、平均値、中央値など)で埋める

# 欠損値を含むサンプル
data_nan = {
    'A': [1, 2, np.nan, 4],
    'B': [5, np.nan, np.nan, 8],
    'C': [9, 10, 11, 12]
}
df_nan = pd.DataFrame(data_nan)
print("\n--- 欠損値を含むDataFrame ---")
print(df_nan)

print("\n--- 欠損値の確認 ---")
print(df_nan.isnull())

# 欠損値を含む行を削除
print("\n--- dropna() 実行後 ---")
print(df_nan.dropna())

# 欠損値を0で埋める
print("\n--- fillna(0) 実行後 ---")
print(df_nan.fillna(0))

# 列'B'の欠損値を列'B'の平均値で埋める
mean_b = df_nan['B'].mean()
print(f"\n--- 列Bの平均値({mean_b:.2f})でfillna ---")
print(df_nan.fillna({'B': mean_b}))
            

データの結合・マージ 🔗

複数のDataFrameを結合する必要がある場合も多いです。PandasはSQLライクな結合操作をサポートしています。

  • pd.concat(): 単純な連結(縦方向または横方向)。軸に沿ってDataFrameを繋ぎ合わせます。
  • pd.merge(): データベースのJOIN操作のように、共通の列またはインデックスをキーとしてDataFrameを結合します。
    • how引数で結合方法を指定: 'left', 'right', 'outer', 'inner' (デフォルト)
    • on引数で結合キーとなる列名を指定。
    • left_on, right_onで左右のDataFrameで異なる名前のキー列を指定。
    • left_index=True, right_index=Trueでインデックスをキーとして結合。
  • df.join(): mergeに似ていますが、主にインデックスベースでの結合を簡潔に行うために使われます。

import pandas as pd

# サンプルDataFrame作成
df_left = pd.DataFrame({
    'key': ['K0', 'K1', 'K2', 'K3'],
    'A': ['A0', 'A1', 'A2', 'A3'],
    'B': ['B0', 'B1', 'B2', 'B3']
})

df_right = pd.DataFrame({
    'key': ['K0', 'K1', 'K4', 'K5'], # K2, K3 と K4, K5 が異なる
    'C': ['C0', 'C1', 'C4', 'C5'],
    'D': ['D0', 'D1', 'D4', 'D5']
})

print("--- 左DataFrame ---")
print(df_left)
print("\n--- 右DataFrame ---")
print(df_right)

# 内部結合 (inner join) - 両方に存在するキーのみ
inner_merged = pd.merge(df_left, df_right, on='key', how='inner')
print("\n--- 内部結合 (inner) ---")
print(inner_merged)

# 左外部結合 (left join) - 左DataFrameのキーをすべて保持
left_merged = pd.merge(df_left, df_right, on='key', how='left')
print("\n--- 左外部結合 (left) ---")
print(left_merged)

# 完全外部結合 (outer join) - 両方のDataFrameのキーをすべて保持
outer_merged = pd.merge(df_left, df_right, on='key', how='outer')
print("\n--- 完全外部結合 (outer) ---")
print(outer_merged)

# concatによる縦連結 (行を追加)
df_concat = pd.concat([df_left, df_right], ignore_index=True) # ignore_index=Trueでインデックスを振り直す
print("\n--- concatによる縦連結 ---")
print(df_concat)
            

グループ化と集計 (Group By) 📊

特定のキーに基づいてデータをグループ化し、各グループに対して集計関数(合計、平均、最大、最小、カウントなど)を適用する操作は、データ分析で非常によく行われます。Pandasのgroupby()メソッドはこれを強力にサポートします。

基本的な流れは「Split-Apply-Combine」です。

  1. Split (分割): DataFrameを指定したキーに基づいてグループに分割する。
  2. Apply (適用): 各グループに対して関数(集計、変換、フィルタリングなど)を適用する。
  3. Combine (結合): 結果を新しいデータ構造(通常はSeriesまたはDataFrame)に結合する。

import pandas as pd

data = {
    '部門': ['営業', '開発', '営業', '開発', '人事', '営業'],
    '社員': ['A', 'B', 'C', 'D', 'E', 'F'],
    '売上': [1000, 500, 1200, 600, 0, 800],
    '残業時間': [20, 10, 25, 15, 5, 18]
}
df = pd.DataFrame(data)
print("--- 元のDataFrame ---")
print(df)

# 部門ごとにグループ化
grouped = df.groupby('部門')

# 各部門の売上合計を計算
print("\n--- 部門別 売上合計 ---")
print(grouped['売上'].sum())

# 各部門の平均残業時間を計算
print("\n--- 部門別 平均残業時間 ---")
print(grouped['残業時間'].mean())

# 複数の集計を一度に行う (aggメソッド)
print("\n--- 部門別 売上合計と平均残業時間 (agg) ---")
print(grouped.agg({
    '売上': 'sum',          # 売上は合計
    '残業時間': 'mean',     # 残業時間は平均
    '社員': 'count'        # 社員数列を使って人数をカウント
}))

# 複数列でグループ化
grouped_multi = df.groupby(['部門', '社員']) # この例ではあまり意味がないが、複数キーでのグループ化を示す
print("\n--- 複数キーでのグループ化 (各社員の合計) ---")
print(grouped_multi.sum())

# applyメソッドでカスタム関数を適用
# 各部門の売上トップの社員を取得する例(少し高度)
def top_sales_person(group):
    return group.loc[group['売上'].idxmax()]

print("\n--- 各部門の売上トップ社員 (apply) ---")
print(grouped.apply(top_sales_person))

            

groupby() は非常に強力で、集計 (sum, mean, count, max, min, agg)、変換 (transform)、フィルタリング (filter) など、様々な操作と組み合わせることができます。

時系列データの扱い ⏳

Pandasは、金融データ、センサーデータ、ログデータなど、時系列データの扱いに非常に優れています。日付や時刻をインデックスとして持つデータを効率的に操作するための機能が豊富に用意されています。

  • 日付/時刻インデックスの作成: pd.to_datetime()で文字列などを日付型に変換し、df.set_index()でインデックスに設定。pd.date_range()で連続した日付インデックスを生成。
  • リサンプリング (Resampling): 時系列データの頻度を変換する(例: 日次データを月次データに集計)。df.resample('頻度').集計関数() (例: df.resample('M').sum() で月次合計)。
  • 時間窓 (Window) 処理: 移動平均 (rolling().mean())、移動合計 (rolling().sum()) など、一定期間のデータに対する計算。
  • 日付要素へのアクセス: df.index.year, df.index.month, df.index.day, df.index.weekdayなどで日付要素を取得。
  • 日付/時刻によるスライシング: df['2024-01'] (2024年1月を抽出), df['2024-01-01':'2024-01-15'] (期間で抽出) など、直感的なスライシングが可能。
  • シフト (Shifting) / ラグ (Lagging): df.shift(n)でデータをn期間ずらす。

import pandas as pd
import numpy as np

# 日付範囲を生成 (2024年の毎日の日付)
dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')

# ランダムなデータでDataFrame作成
data = np.random.randn(len(dates))
ts = pd.Series(data, index=dates)
df_ts = pd.DataFrame({'Value': ts})

print("--- 時系列データ (最初の5日) ---")
print(df_ts.head())

# 月次での合計にリサンプリング
print("\n--- 月次合計 (resample) ---")
print(df_ts.resample('M').sum()) # 'M'は月末を表す

# 7日間の移動平均を計算
print("\n--- 7日間移動平均 (rolling mean) ---")
rolling_mean = df_ts['Value'].rolling(window=7).mean()
print(rolling_mean.head(10)) # 最初の数日はNaNになる

# 特定期間のデータを抽出
print("\n--- 2024年3月のデータを抽出 ---")
print(df_ts['2024-03'])

# データを1日前にシフト
print("\n--- データを1日シフト (shift) ---")
print(df_ts.shift(1).head())

            

簡単な可視化 📈

Pandas自体にも、matplotlibライブラリを内部的に利用した簡単なプロット機能が備わっています。.plot()メソッドを使うことで、DataFrameやSeriesから直接グラフを生成できます。

  • df.plot(): 線グラフ(デフォルト)
  • df.plot(kind='bar'): 棒グラフ
  • df.plot(kind='hist'): ヒストグラム
  • df.plot(kind='box'): 箱ひげ図
  • df.plot(kind='scatter', x='列A', y='列B'): 散布図
  • df.plot(kind='pie', y='列名'): 円グラフ

より高度な可視化を行いたい場合は、matplotlibseabornといった専門の可視化ライブラリと組み合わせて使用するのが一般的です。


import pandas as pd
import numpy as np
# Jupyter Notebookや類似の環境では以下のマジックコマンドが必要な場合がある
# %matplotlib inline
import matplotlib.pyplot as plt # 可視化のためにインポート (plot()内部で使われる)

# サンプルデータ
data = {
    'A': np.random.rand(10),
    'B': np.random.rand(10),
    'C': np.random.rand(10)
}
df_plot = pd.DataFrame(data)

# 線グラフ
print("\n--- 線グラフ表示 (別途ウィンドウが開くかインライン表示されます) ---")
df_plot.plot()
plt.title('Line Plot') # タイトル追加 (matplotlibの機能)
# plt.show() # スクリプト実行の場合は必要

# 棒グラフ
print("--- 棒グラフ表示 ---")
df_plot.plot(kind='bar')
plt.title('Bar Plot')
# plt.show()

# ヒストグラム (列'A')
print("--- ヒストグラム表示 (列A) ---")
df_plot['A'].plot(kind='hist', bins=5) # binsで区間の数を指定
plt.title('Histogram of Column A')
# plt.show()

# 散布図 (A列 vs B列)
print("--- 散布図表示 (A vs B) ---")
df_plot.plot(kind='scatter', x='A', y='B')
plt.title('Scatter Plot (A vs B)')
# plt.show()
            
注意: 上記のコードを通常のPythonスクリプトとして実行した場合、グラフを表示するためには各プロットの後に plt.show() を呼び出す必要があります。Jupyter Notebookなどの環境では、%matplotlib inline を実行しておくと、セル実行時にグラフがインライン表示されることが多いです。

パフォーマンスの向上と注意点 🚀

Pandasは非常に便利ですが、大規模なデータを扱う際にはパフォーマンスが問題になることがあります。効率的なコードを書くためのヒントをいくつか紹介します。

  • ループ処理を避ける: PythonのforループでDataFrameの行を反復処理するのは非常に遅いです。可能な限り、Pandasが提供するベクトル化された操作(列全体に対する演算など)や、apply(), map(), applymap(), groupby() などの組み込み関数を使用しましょう。これらは内部的にC言語で最適化されているため高速です。
  • 適切なデータ型を使用する:
    • 数値データに対してint64float64がデフォルトで使われることが多いですが、データの範囲が許せばint32, float32や、さらに小さいint16, int8などを使うことでメモリ使用量を削減できます。
    • カテゴリカルデータ(種類が限定される文字列など、例: 性別、地域名)には、category型を使うとメモリ効率が大幅に向上します。df['列名'].astype('category')で変換できます。
    • Pandas 1.0以降で導入されたStringDtype ('string') は、従来のobject型よりも文字列操作に適しており、欠損値(pd.NA)の扱いも改善されています。
    • convert_dtypes()メソッドを使うと、Pandasが自動で最適なデータ型(IntegerArray, StringDtypeなど)に変換してくれる場合があります。
  • 不要なデータは早期に削除: 分析に必要な列や行だけを選択し、不要なデータは早い段階で削除することで、メモリ使用量と処理時間を削減できます。
  • チャンク処理: 巨大なファイルを一度にメモリに読み込むのが難しい場合は、read_csv()chunksize引数を使って、ファイルを少しずつ読み込んで処理することができます。
  • ライブラリの活用: NumPy関数を直接使う、Numbaで特定の関数を高速化する、DaskVaexPolarsModinなどの並列処理や大規模データ処理に特化したライブラリの利用を検討する。最近では、Pandas API互換で高速化を目指すFireDucksのようなライブラリも登場しています (2024年時点)。
コピーとビュー (Copy vs. View): Pandasの操作では、元のDataFrameの「ビュー(参照)」が返されるか、「コピー」が返されるかが状況によって異なります。ビューに対して変更を行うと、意図せず元のDataFrameが変更されてしまうことがあります(SettingWithCopyWarning)。これを避けるためには、明示的に.copy()メソッドを使うか、.loc[].iloc[]を使った代入を推奨します。

まとめ ✨

Pandasは、Pythonにおけるデータ分析の中心的なライブラリであり、データの読み込み、整形、加工、集計、可視化といった幅広い機能を提供します。SeriesとDataFrameという強力なデータ構造を基礎として、直感的かつ効率的なデータ操作を実現します。

この記事では、Pandasの基本的な概念から、データ操作、結合、グループ化、時系列処理、簡単な可視化、パフォーマンスに関するヒントまでを駆け足で紹介しました。しかし、Pandasにはここで紹介しきれなかった機能がまだまだたくさんあります。

データ分析や機械学習の道を進む上で、Pandasを使いこなすことは非常に重要です。ぜひ公式ドキュメント (https://pandas.pydata.org/docs/) などを参照しながら、さらに深く学んでみてください。実際に手を動かして様々なデータを扱ってみることが、スキル習得への一番の近道です!🚀

コメント

タイトルとURLをコピーしました