0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pandas基礎から実践データ処理ガイド

0
Last updated at Posted at 2026-05-28

Pandas の基本的なデータ構造、インデックス操作、集計、欠損値処理、結合、グループ化について、順を追って学べるように整理しています。

参考として、次の記事も参照してください。

NumPyライブラリ紹介
Matplotlibライブラリ紹介
データサイエンティストのためのPythonライブラリ応用(上級編)

導読

  • この文は、Pandas を使う理由 -> オブジェクトを作る -> DataFrame を読む -> 計算と集計を行う -> 欠損値を処理する -> データを結合する -> グループ化とピボットテーブルへ進む、という流れで Pandas の全体像を最初に押さえるためのメモです。
  • 読み始めるときは、まず SeriesDataFrameindexcolumnslocilocisnull()groupby()merge()pivot_table() を優先して押さえると、その後の分析コードをかなり読みやすくできます。
  • NumPy が数値計算の土台を担うのに対して、Pandas はラベル付きデータを扱うための実務的な操作をまとめて担います。列名や行名を持ったまま集計や結合ができるのが大きな強みです。

この文の章立ては、次のようになっています。

  1. Pandas を使う理由
    • NumPy と Pandas の役割の違い: NumPy が得意なことと、Pandas が補う部分を整理します。
  2. Pandas オブジェクトの作成
    • Series を作る: pd.Series() を使って、リスト、NumPy 配列、辞書、スカラーから作成します。
    • DataFrame を作る: pd.DataFrame() を使って、Series、辞書、辞書のリスト、二次元 NumPy 配列から作成します。
  3. DataFrame の性質と基本操作
    • 属性を確認する: valuesindexcolumnsshapesizedtypes を確認します。
    • インデックスで取り出す: 列、行、スカラーを []lociloc で取り出します。
    • スライスと選択: 行切り出し、列切り出し、行列同時指定、散在した選択を行います。
    • ブールインデックスと代入: 条件抽出、isin()、新しい列の追加、値の更新、indexcolumns の変更を扱います。
  4. 数値演算と統計分析
    • データを確認する: head()tail()info() で全体をすばやく確認します。
    • NumPy 関数をそのまま使う: ベクトル化演算、行列演算、ブロードキャストを Pandas 上で行います。
    • インデックスの自動整列: ラベルに基づいて演算がそろう仕組みを確認します。
    • 並べ替えと統計量: value_counts()sort_values()sort_index()count()sum()mean()describe()corr() などを扱います。
    • apply() で自作集計を行う: 列方向(横)や行方向(縦)に独自の関数を適用します。
  5. 欠損値処理
    • 欠損値を見つける: isnull()notnull()dtypes を見ながら欠損を確認します。
    • 欠損値を削除する: dropna() で行や列を落とします。
    • 欠損値を埋める: fillna() で定数や平均値を使って補完します。
  6. データの結合
    • concat() で縦横に結合する: 行方向(縦)、列方向(横)、重複インデックス時の扱いを見ます。
    • merge() でキーをそろえて結合する: 共通キーをもつ表を結び付けます。
  7. グループ化とピボットテーブル
    • groupby() の基本: 集約、反復、aggregate()filter()transform()apply() を確認します。
    • グループキーを工夫する: リスト、辞書、関数、複数キーでのグループ化を扱います。
    • 実例で理解する: 惑星データとタイタニックデータで groupby()pivot_table() を使います。
  8. そのほかの機能
    • 文字列、時系列、多重インデックス: Pandas の応用範囲を見渡します。
    • eval()query(): 式評価や条件抽出を高速化する方法と使いどころを確認します。

Pandas を使う理由

NumPy は、ベクトル化された数値計算を非常に高速に処理できます。一方で、実務のデータ分析では、列名を付ける、欠損値を扱う、カテゴリごとに集計する、ピボットテーブルを作るといった操作が頻繁に必要になります。

Pandas は NumPy を土台にしつつ、こうしたラベル付きデータの操作を扱いやすくしたライブラリです。数値だけではなく、文字列や日時を含む表形式データを一貫した書き方で扱えるのが強みです。

import numpy as np
import pandas as pd

# NumPy は数値配列の計算に強い
x = np.arange(6).reshape(2, 3)
print(x)
print(x.sum(axis=0))

# Pandas は列名を持った表として扱える
df = pd.DataFrame(x, columns=["A", "B", "C"])
print(df)
print(df.sum())

Pandas オブジェクトの作成

この章では、Pandas の基本となる SeriesDataFrame をどのように作るかを整理します。どんな入力から作れるかを把握しておくと、CSV 読み込み後の変換や手元での検証コードが書きやすくなります。

Series を作る

Series は、ラベル付きの一次元配列です。値の並びだけではなく、各値に index が付いているのが特徴です。

共通の形は次のとおりです。

pd.Series(data, index=index, dtype=dtype)

data にはリスト、辞書、NumPy 配列、スカラーを渡せます。indexdtype はどちらも省略可能です。

リストから作る

まずはリストから作るのが基本です。index を省略すると 0 から始まる整数インデックスが付きます。

import pandas as pd

# index を省略すると 0 から始まる整数が付く
data = pd.Series([1.5, 3, 4.5, 6])
print(data)

# index を自分で付けることもできる
data = pd.Series([1.5, 3, 4.5, 6], index=["a", "b", "c", "d"])
print(data)

# dtype を指定して型をそろえる
data = pd.Series([1, 2, 3, 4], index=["a", "b", "c", "d"], dtype="float")
print(data)

Series は異なる型の値も持てますが、その場合は object 型として扱われることがあります。数値として扱いたいときは dtype をそろえる意識が重要です。

import pandas as pd

# 文字列が混ざると object 型になることがある
data = pd.Series([1, 2, "3", 4], index=["a", "b", "c", "d"])
print(data)
print(data["a"])
print(data["c"])

# 変換可能な文字列なら dtype=float を指定して数値化できる
data = pd.Series([1, 2, "3", 4], index=["a", "b", "c", "d"], dtype=float)
print(data)
print(data["c"])

# 変換できない値を含むとエラーになる
# pd.Series([1, 2, "a", 4], index=["a", "b", "c", "d"], dtype=float)

NumPy 配列から作る

一次元の NumPy 配列からも、そのまま Series を作れます。

import numpy as np
import pandas as pd

# NumPy の一次元配列からも Series を作れる
x = np.arange(5)
print(pd.Series(x))

辞書から作る

辞書を渡すと、キーが index、値が data として使われます。index を指定すると、辞書に存在しないキーは NaN で埋められます。

import pandas as pd

population_dict = {
	"東京": 3341,
	"大阪": 1296,
	"名古屋": 714,
	"福岡": 222,
}

# 辞書のキーが index、値がデータになる
population = pd.Series(population_dict)
print(population)

# 存在しないラベルは NaN で埋まる
population = pd.Series(population_dict, index=["東京", "名古屋", "神戸", "仙台"])
print(population)

スカラーから作る

値がスカラーの場合は、指定した index の長さに合わせて同じ値が並びます。

import pandas as pd

# スカラーを渡すと index の長さだけ同じ値が並ぶ
print(pd.Series(5, index=[100, 200, 300]))

DataFrame を作る

DataFrame は、行と列のラベルを持った二次元の表形式データです。Pandas で最もよく使う構造で、分析の中心になります。

共通の形は次のとおりです。

pd.DataFrame(data, index=index, columns=columns)

Series から作る

Series ひとつからも DataFrame を作れます。列名を後から指定することもできます。

import pandas as pd

population_dict = {
	"東京": 3341,
	"大阪": 1296,
	"名古屋": 714,
	"福岡": 222,
}

population = pd.Series(population_dict)
# Series 1 本からでも DataFrame を作れる
print(pd.DataFrame(population))
# columns で列名を明示する
print(pd.DataFrame(population, columns=["population"]))

Series の辞書から作る

複数の Series を辞書にして渡すと、列ごとの意味を持った DataFrame ができます。長さが合わない部分は自動的に NaN で補われます。スカラーを渡すと、その値が全行へ広がります。

import pandas as pd

population_dict = {
	"東京": 3341,
	"大阪": 1296,
	"名古屋": 714,
	"福岡": 222,
}
GDP_dict = {
	"東京": 270,
	"大阪": 95,
	"名古屋": 70,
	"福岡": 30,
}

population = pd.Series(population_dict)
GDP = pd.Series(GDP_dict)

# 列ごとの意味を持った表になる
print(pd.DataFrame({"population": population, "GDP": GDP}))
# スカラーは全行へ自動的に広がる
print(pd.DataFrame({"population": population, "GDP": GDP, "country": "日本"}))

辞書のリストから作る

行単位の辞書を並べると、そのまま表として読み込めます。キーがそろっていない部分は NaN になります。

import pandas as pd

# 行単位の辞書を並べると表として作成できる
data = [{"a": i, "b": 2 * i} for i in range(3)]
data = pd.DataFrame(data)
print(data)

# 列を取り出してコピーしてから変更する
data1 = data["a"].copy()
print(data1)
data1[0] = 10
print(data1)
print(data)

# キーがそろわない部分は NaN になる
data = [{"a": 1, "b": 1}, {"b": 3, "c": 4}]
print(pd.DataFrame(data))

二次元 NumPy 配列から作る

NumPy の二次元配列を、そのまま DataFrame に載せることもできます。このとき columnsindex を指定すると表らしく扱いやすくなります。

import numpy as np
import pandas as pd

data = np.random.randint(10, size=(3, 2))
print(data)
# columns と index を付けると表として読みやすくなる
print(pd.DataFrame(data, columns=["foo", "bar"], index=["a", "b", "c"]))

DataFrame の性質と基本操作

この章では、作成した DataFrame をどう読むか、どう取り出すか、どう更新するかをまとめます。ここを理解すると、Pandas を「ラベル付きの表」として自然に扱えるようになります。

属性を確認する

DataFrame を受け取ったら、まず valuesindexcolumnsshapesizedtypes を確認すると全体像をつかみやすくなります。

import pandas as pd

population_dict = {
	"東京": 3341,
	"大阪": 1296,
	"名古屋": 714,
	"福岡": 222,
}
GDP_dict = {
	"東京": 270,
	"大阪": 95,
	"名古屋": 70,
	"福岡": 30,
}

population = pd.Series(population_dict)
GDP = pd.Series(GDP_dict)
data = pd.DataFrame({"pop": population, "GDP": GDP})

# DataFrame の基本属性を順に確認する
print(data)
print(data.values)
print(data.index)
print(data.columns)
print(data.shape)
print(data.size)
print(data.dtypes)

列、行、スカラーを取り出す

Pandas のインデックス操作では、列、行、スカラーの取り出し方を分けて覚えると整理しやすくなります。

列は [] でも属性形式でも取り出せますが、確実なのは data["列名"] です。

import pandas as pd

population_dict = {
	"東京": 3341,
	"大阪": 1296,
	"名古屋": 714,
	"福岡": 222,
}
GDP_dict = {
	"東京": 270,
	"大阪": 95,
	"名古屋": 70,
	"福岡": 30,
}

population = pd.Series(population_dict)
GDP = pd.Series(GDP_dict)
data = pd.DataFrame({"pop": population, "GDP": GDP})

# 列を 1 本取り出す
print(data["pop"])
# 複数列もまとめて指定できる
print(data[["GDP", "pop"]])
# 属性形式でも取れるが、列名と衝突しない前提になる
print(data.GDP)

行はラベルベースの loc と、位置ベースの iloc を使い分けます。スカラーを取り出すときも loc[row, col]iloc[row, col] が基本です。

import pandas as pd

population_dict = {
	"東京": 3341,
	"大阪": 1296,
	"名古屋": 714,
	"福岡": 222,
}
GDP_dict = {
	"東京": 270,
	"大阪": 95,
	"名古屋": 70,
	"福岡": 30,
}

population = pd.Series(population_dict)
GDP = pd.Series(GDP_dict)
data = pd.DataFrame({"pop": population, "GDP": GDP})

# loc はラベル指定
print(data.loc["東京"])
print(data.loc[["東京", "名古屋"]])

# iloc は位置指定
print(data.iloc[0])
print(data.iloc[[1, 3]])

# スカラーも行と列を組み合わせて取得する
print(data.loc["東京", "GDP"])
print(data.iloc[0, 1])
print(data.values[0][1])

print(type(data.GDP))
print(GDP)
print(GDP["東京"])

スライスと複合的な選択

日付インデックスを持つ DataFrame では、行スライスや列スライスを直感的に書けます。loc はラベルベース、iloc は位置ベースです。

import numpy as np
import pandas as pd

dates = pd.date_range(start="2019-01-01", periods=6)
df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=["A", "B", "C", "D"])
print(df)

# 日付ラベルで行を切り出す
print(df["2019-01-01":"2019-01-03"])
print(df.loc["2019-01-01":"2019-01-03"])
print(df.iloc[0:3])

# 列方向(横)のスライス
print(df.loc[:, "A":"C"])
print(df.iloc[:, 0:3])

# 行と列を同時に指定する
print(df.loc["2019-01-02":"2019-01-03", "C":"D"])
print(df.iloc[1:3, 2:])

# 離れた行や列も選べる
print(df.loc["2019-01-04":"2019-01-06", ["A", "C"]])
print(df.iloc[3:, [0, 2]])

print(df.loc[["2019-01-02", "2019-01-06"], "C":"D"])
print(df.iloc[[1, 5], 0:3])

print(df.loc[["2019-01-04", "2019-01-06"], ["A", "D"]])
print(df.iloc[[1, 5], [0, 3]])

ブールインデックス

条件式をそのまま DataFrame や Series にかけると、ブール配列を使って行やセルを抽出できます。isin() も実務でよく使う書き方です。

import numpy as np
import pandas as pd

dates = pd.date_range(start="2019-01-01", periods=6)
df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=["A", "B", "C", "D"])
print(df)
# 条件式そのものが True / False の表になる
print(df > 0)
# True の場所だけ値を残す
print(df[df > 0])
print(df.A > 0)
# 列 A が正の行だけ残す
print(df[df.A > 0])

df2 = df.copy()
df2["E"] = ["one", "one", "two", "three", "four", "three"]
print(df2)

# isin() で複数候補に一致する行を調べる
ind = df2["E"].isin(["two", "four"])
print(ind)
print(df2[ind])

代入とラベルの更新

Pandas では、新しい列の追加、セルの更新、indexcolumns の再設定も簡単に行えます。

import numpy as np
import pandas as pd

dates = pd.date_range(start="2019-01-01", periods=6)
df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=["A", "B", "C", "D"])
print(df)

s1 = pd.Series([1, 2, 3, 4, 5, 6], index=pd.date_range("20190101", periods=6))
# 新しい列を追加する
df["E"] = s1
print(df)

# 特定セルや列全体を更新する
df.loc["2019-01-01", "A"] = 0
df.iloc[0, 1] = 0
df["D"] = np.array([5] * len(df))
print(df)

# index や columns 自体も付け替えられる
df.index = [i for i in range(len(df))]
print(df)

df.columns = [i for i in range(df.shape[1])]
print(df)

数値演算と統計分析

Pandas は表形式データの操作が得意ですが、NumPy の計算能力とも非常に相性がよいです。この章では、確認系メソッド、数値演算、統計量、apply() をまとめて見ます。

データをすばやく確認する

head()tail()info() は、データを読み込んだ直後に最初に使う定番の確認方法です。

import numpy as np
import pandas as pd

dates = pd.date_range(start="2019-01-01", periods=6)
df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=["A", "B", "C", "D"])
print(df)
# 先頭と末尾をざっと見る
print(df.head())
print(df.head(2))
print(df.tail())
print(df.tail(3))

df.iloc[0, 3] = np.nan
print(df)
# info() で件数や型、欠損の有無を確認する
print(df.info())

NumPy の関数は Pandas にもそのまま使える

NumPy のユニバーサル関数は、Pandas の SeriesDataFrame に対してもそのまま使えることが多いです。四則演算や指数関数などはそのまま書けます。

import numpy as np
import pandas as pd

x = pd.DataFrame(np.arange(4).reshape(1, 4))
print(x)
# スカラー演算や ufunc はそのまま使える
print(x + 5)
print(np.exp(x))

y = pd.DataFrame(np.arange(4, 8).reshape(1, 4))
print(y)
# 同じ形なら要素ごとの積になる
print(x * y)

行列演算と NumPy との比較

DataFrame に対しても転置や行列積を行えます。ただし、純粋な計算速度だけを見ると NumPy の方が速いことが多く、Pandas はラベル付きデータ処理に強みがあります。

import numpy as np
import pandas as pd

np.random.seed(42)
x = pd.DataFrame(np.random.randint(10, size=(30, 30)))
z = x.T
print(z)

np.random.seed(1)
y = pd.DataFrame(np.random.randint(10, size=(30, 30)))
# dot() で行列積を計算する
print(x.dot(y))

# Pandas と NumPy の速度を比べる
%timeit x.dot(y)
%timeit np.dot(x, y)

x1 = np.array(x)
y1 = np.array(y)
%timeit x1.dot(y1)
%timeit np.dot(x1, y1)
%timeit np.dot(x.values, y.values)

x2 = list(x1)
y2 = list(y1)
x3 = []
y3 = []
for i in x2:
	row = []
	for j in i:
		row.append(int(j))
	x3.append(row)
for i in y2:
	row = []
	for j in i:
		row.append(int(j))
	y3.append(row)

def f(x, y):
	res = []
	# 三重ループで行列積を自前実装する
	for i in range(len(x)):
		row = []
		for j in range(len(y[0])):
			sum_row = 0
			for k in range(len(x[0])):
				sum_row += x[i][k] * y[k][j]
			row.append(sum_row)
		res.append(row)
	return res

%timeit f(x3, y3)

計算だけに注目すると NumPy の方が有利ですが、列名やインデックスを保ったまま処理したいときは Pandas が便利です。

ブロードキャスト

Pandas でもブロードキャストに近い計算ができ、行方向(縦)・列方向(横)のどちらにそろえるかを axis で明示できます。

import numpy as np
import pandas as pd

np.random.seed(42)
x = pd.DataFrame(np.random.randint(10, size=(3, 3)), columns=list("ABC"))
print(x)

# 1 行目を基準に各列を割る
print(x.iloc[0])
print(x / x.iloc[0])

# axis=0 は行方向(縦)へそろえて計算する
print(x.A)
print(x.div(x.A, axis=0))
# axis=1 は列方向(横)へそろえて計算する
print(x.div(x.iloc[0], axis=1))

インデックスの自動整列

Pandas の演算では、値の位置ではなく indexcolumns のラベルが自動的にそろえられます。存在しない組み合わせは NaN になります。

import numpy as np
import pandas as pd

A = pd.DataFrame(np.random.randint(0, 20, size=(2, 2)), columns=list("AB"))
B = pd.DataFrame(np.random.randint(0, 10, size=(3, 3)), columns=list("ABC"))

print(A)
print(B)
# ラベルが一致する場所だけで計算される
print(A + B)
# fill_value を使うと欠けた場所を補って計算できる
print(A.add(B, fill_value=0))
print(A * B)

並べ替えと統計量

Pandas は、カテゴリ数の集計、行列の並べ替え、記述統計量の取得を一連で書けます。ここは実務でかなり使用頻度が高い部分です。

import numpy as np
import pandas as pd
from collections import Counter

y = np.random.randint(3, size=20)
print(y)
print(np.unique(y))
print(Counter(y))

y1 = pd.DataFrame(y, columns=["A"])
print(y1)
# value_counts() でカテゴリ数を数える
print(np.unique(y1))
print(y1["A"].value_counts())
import numpy as np
import pandas as pd

population_dict = {
	"東京": 3341,
	"大阪": 1296,
	"名古屋": 714,
	"福岡": 222,
}
GDP_dict = {
	"東京": 270,
	"大阪": 95,
	"名古屋": 70,
	"福岡": 30,
}

population = pd.Series(population_dict)
GDP = pd.Series(GDP_dict)
city_info = pd.DataFrame({"population": population, "GDP": GDP})
city_info["per_GDP"] = city_info["GDP"] / city_info["population"]

print(city_info)
# by は並べ替えの基準列、ascending=False は降順を表す
print(city_info.sort_values(by="per_GDP"))
print(city_info.sort_values(by="per_GDP", ascending=False))
import numpy as np
import pandas as pd

data = pd.DataFrame(np.random.randint(20, size=(3, 4)), index=[2, 1, 0], columns=list("CBAD"))
print(data)
# axis=1 は列方向(横)、ascending=False は逆順で並べ替える指定
print(data.sort_index())
print(data.sort_index(axis=1))
print(data.sort_index(axis=1, ascending=False))
import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.normal(2, 4, size=(6, 4)), columns=list("ABCD"))
print(df)
# 列ごとの代表的な統計量を確認する
print(df.count())
print(df.sum())
print(df.sum(axis=1))
print(df.min())
print(df.max(axis=1))
print(df.idxmax())
print(df.mean())
print(df.var())
print(df.std())
print(df.median())

data = pd.DataFrame(np.random.randint(5, size=(10, 2)), columns=list("AB"))
print(data)
print(data.mode())

print(df.quantile(0.75))
print(df.describe())

data_2 = pd.DataFrame(
	[["a", "a", "c", "d"], ["c", "a", "c", "b"], ["a", "a", "d", "c"]],
	columns=list("ABCD"),
)
print(data_2)
print(data_2.describe())

print(df.corr())
print(df.corrwith(df["A"]))

apply() で独自の処理をまとめる

apply() を使うと、列ごと、または行ごとに同じ関数を適用できます。NumPy の関数も自作関数も渡せます。

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.normal(2, 4, size=(6, 4)), columns=list("ABCD"))
print(df)
# NumPy 関数も apply() に渡せる
print(df.apply(np.cumsum))
# axis=1 を付けると列ごとではなく行ごとに処理する
print(df.apply(np.cumsum, axis=1))
print(df.apply(sum))
print(df.sum())
print(df.apply(lambda x: x.max() - x.min()))

def my_describe(x):
	# 自分で欲しい統計量だけを返す
	return pd.Series(
		[x.count(), x.mean(), x.max(), x.idxmin(), x.std()],
		index=["count", "mean", "max", "idxmin", "std"],
	)

print(df.apply(my_describe))

欠損値処理

実務データでは、欠損値の扱いがほぼ必ず必要になります。Pandas は欠損の発見、削除、補完までをまとめて行えます。

欠損値を見つける

Nonenp.nan が混ざると、列の型が object に寄ることがあります。まずは dtypesisnull() を見て、どこに欠損があるかを確認します。

import numpy as np
import pandas as pd

data = pd.DataFrame(
	np.array([[1, np.nan, 2], [np.nan, 3, 4], [5, 6, None]]),
	columns=["A", "B", "C"],
)
print(data)
# 型と欠損位置をまとめて確認する
print(data.dtypes)
print(data.isnull())
print(data.notnull())

欠損値を削除する

dropna() は、欠損を含む行や列を落としたいときに使います。axishow を組み合わせると、細かく条件を指定できます。

import numpy as np
import pandas as pd

data = pd.DataFrame(
	np.array([
		[1, np.nan, 2, 3],
		[np.nan, 4, 5, 6],
		[7, 8, np.nan, 9],
		[10, 11, 12, 13],
	]),
	columns=["A", "B", "C", "D"],
)
print(data)
print(data.dtypes)

# 既定では axis=0 なので、欠損を含む行が落ちる
print(data.dropna())
# axis="columns" を指定すると列方向(横)を判定対象にする
print(data.dropna(axis="columns"))

data["D"] = np.nan
print(data)
# how="all" は全要素が欠損の列だけ削除、how="any" は 1 つでも欠損があれば削除
print(data.dropna(axis="columns", how="all"))
print(data.dropna(axis="columns", how="any"))

data.loc[3] = np.nan
print(data)
# how="all" なので、全部欠損の行だけが対象になる
print(data.dropna(how="all"))

欠損値を埋める

欠損を落としたくないときは fillna() を使います。固定値、列ごとの平均、全体平均など、状況に応じて埋め方を変えられます。

import numpy as np
import pandas as pd

data = pd.DataFrame(
	np.array([
		[1, np.nan, 2, 3],
		[np.nan, 4, 5, 6],
		[7, 8, np.nan, 9],
		[10, 11, 12, 13],
	]),
	columns=["A", "B", "C", "D"],
)
print(data)
# value=5 のようにスカラーを渡すと、すべて同じ値で埋める
print(data.fillna(value=5))

# value に Series を渡すと、同名列ごとに別の値で埋められる
fill = data.mean()
print(fill)
print(data.fillna(value=fill))

# stack() で 1 本の Series にして全体平均を計算する
fill = data.stack().mean()
print(fill)
print(data.fillna(value=fill))

データの結合

複数の DataFrame をまとめるときは、concat()merge() が基本になります。concat() は縦横に積み重ねる操作、merge() は共通キーで表を結び付ける操作です。

concat() で縦横に結合する

まずは練習用に DataFrame を返す小さな関数を用意しておくと、挙動を確認しやすくなります。

import pandas as pd

def make_df(cols, ind):
	# 列名とインデックスから簡単な表を作る補助関数
	data = {c: [str(c) + str(i) for i in ind] for c in cols}
	return pd.DataFrame(data, ind)

print(make_df("ABC", range(3)))

縦方向の結合は axis=0、横方向の結合は axis=1 です。

import pandas as pd

def make_df(cols, ind):
	data = {c: [str(c) + str(i) for i in ind] for c in cols}
	return pd.DataFrame(data, ind)

df_1 = make_df("AB", [1, 2])
df_2 = make_df("AB", [3, 4])
print(df_1)
print(df_2)
# axis=0 が既定で、行方向(縦)へ積み重ねる
print(pd.concat([df_1, df_2]))

df_3 = make_df("AB", [0, 1])
df_4 = make_df("CD", [0, 1])
print(df_3)
print(df_4)
# axis=1 を指定すると列方向(横)へ連結する
print(pd.concat([df_3, df_4], axis=1))

インデックスや列名が重なる場合は、ignore_index=True などで整理できます。

import pandas as pd

def make_df(cols, ind):
	data = {c: [str(c) + str(i) for i in ind] for c in cols}
	return pd.DataFrame(data, ind)

df_5 = make_df("AB", [1, 2])
df_6 = make_df("AB", [1, 2])
# ignore_index=True で既存 index を捨てて 0, 1, 2... を振り直す
print(pd.concat([df_5, df_6]))
print(pd.concat([df_5, df_6], ignore_index=True))

df_7 = make_df("ABC", [1, 2])
df_8 = make_df("BCD", [1, 2])
print(pd.concat([df_7, df_8], axis=1))
print(pd.concat([df_7, df_8], axis=1, ignore_index=True))

merge() でキーをそろえて結合する

merge() は SQL の JOIN に近い考え方で、共通する列をキーにして表を結合します。

import pandas as pd

def make_df(cols, ind):
	data = {c: [str(c) + str(i) for i in ind] for c in cols}
	return pd.DataFrame(data, ind)

df_9 = make_df("AB", [1, 2])
df_10 = make_df("BC", [1, 2])
print(df_9)
print(df_10)
# 共通列 B をキーにする内部結合で、両方にある行だけ残る
print(pd.merge(df_9, df_10))

df_9 = make_df("AB", [1, 2])
df_10 = make_df("CB", [2, 1])
print(df_9)
print(df_10)
print(pd.merge(df_9, df_10))

都市データのような実例で見ると、どの行が残るかを理解しやすくなります。how="outer" を使うと、どちらか一方にしか存在しないキーも残せます。

import pandas as pd

population_dict = {
	"city": ("東京", "名古屋", "福岡"),
	"pop": (270, 70, 30),
}
population = pd.DataFrame(population_dict)

GDP_dict = {
	"city": ("東京", "大阪", "名古屋"),
	"GDP": (30320, 32680, 13468),
}
GDP = pd.DataFrame(GDP_dict)

print(population)
print(GDP)
# how を省略すると内部結合で、両方にある都市だけ残る
print(pd.merge(population, GDP))
# how="outer" にすると片方だけにある都市も残る
print(pd.merge(population, GDP, how="outer"))

グループ化とピボットテーブル

groupby() は Pandas の中でも特に重要な機能です。カテゴリごとに集計したり、グループ単位で変換したりできます。ピボットテーブルは、その集計結果を見やすい二次元表へ整えるための道具です。

groupby() の基本

groupby() は呼び出しただけでは結果を計算せず、集約メソッドを付けて初めて結果を返します。これが遅延評価のような使い方です。

import numpy as np
import pandas as pd

df = pd.DataFrame({
	"key": ["A", "B", "C", "C", "B", "A"],
	"data1": range(6),
	"data2": np.random.randint(0, 10, size=6),
})
print(df)

# groupby オブジェクトを作るだけではまだ集計されない
print(df.groupby("key"))
print(df.groupby("key").sum())
print(df.groupby("key").mean())

# グループごとに中身を見てみる
for group in df.groupby("key"):
	print(str(group))

# 特定列だけを抜き出して集計する
print(df.groupby("key")["data2"].sum())

for data, group in df.groupby("key"):
	print("{0:5} shape={1}".format(data, group.shape))

print(df.groupby("key")["data1"].describe())
print(df.groupby("key").aggregate(["min", "median", "max"]))

filter()、transform()、apply()

filter() は条件に合うグループだけを残し、transform() は元の行数を保ったままグループ単位の変換を返します。apply() はさらに自由度が高く、任意の処理をまとめて書けます。

import numpy as np
import pandas as pd

df = pd.DataFrame({
	"key": ["A", "B", "C", "C", "B", "A"],
	"data1": range(6),
	"data2": np.random.randint(0, 10, size=6),
})

def filter_func(x):
	# 標準偏差が 3 を超えるグループだけ残す
	return x["data2"].std() > 3

print(df.groupby("key")["data2"].std())
print(df.groupby("key").filter(filter_func))

# transform() は元の行数を保ったまま返す
print(df.groupby("key").transform(lambda x: x - x.mean()))
print(df.groupby("key").apply(lambda x: x - x.mean()))

def norm_by_data2(x):
	# グループ内で data2 の合計を使って正規化する
	x = x.copy()
	x["data1"] /= x["data2"].sum()
	return x

print(df.groupby("key").apply(norm_by_data2))

グループキーを工夫する

groupby() のキーは列名だけではありません。リスト、辞書、関数、複数キーの組み合わせも使えます。

import numpy as np
import pandas as pd

df = pd.DataFrame({
	"key": ["A", "B", "C", "C", "B", "A"],
	"data1": range(6),
	"data2": np.random.randint(0, 10, size=6),
})

L = [0, 1, 0, 1, 2, 0]
print(df)
# リストをグループキーとして使う
print(df.groupby(L).sum())

df2 = df.set_index("key")
mapping = {"A": "first", "B": "constant", "C": "constant"}
print(df2)
# 辞書や関数もグループキーにできる
print(df2.groupby(mapping).sum())
print(df2.groupby(str.lower).mean())
print(df2.groupby([str.lower, mapping]).mean())

実例 1: 惑星観測データを decade ごとに集計する

実データを使うと、groupby() の意味がかなりつかみやすくなります。ここでは seaborn の惑星データを使い、発見方法と年代ごとに観測数をまとめます。

import seaborn as sns

planets = sns.load_dataset("planets")
# まずはデータの大きさと概要を見る
print(planets.shape)
print(planets.head())
print(planets.describe())

# year から decade 列を作る
decade = 10 * (planets["year"] // 10)
print(decade.head())

decade = decade.astype(str) + "s"
decade.name = "decade"
print(decade.head())

# 発見方法と年代ごとの観測数を集計する
print(planets.groupby(["method", decade]).sum())
print(planets.groupby(["method", decade])[["number"]].sum().unstack().fillna(0))

実例 2: タイタニックデータとピボットテーブル

groupby()pivot_table() はかなり近いことができます。違いは、pivot_table() の方が集計表として読みやすい形を短く書きやすいことです。

import seaborn as sns

titanic = sns.load_dataset("titanic")
print(titanic.head())

# 年齢を 10 歳刻みへ丸める
T = titanic[titanic.age.notnull()].copy()
Age = 10 * (T["age"] // 10)
Age = Age.astype(int)
print(Age.head())
print((Age.astype(str) + "s").head())

# groupby でもクロス集計の形を作れる
print(T.groupby(["sex", Age])["survived"].mean().unstack())

T.age = Age
# pivot_table() なら同様の集計を短く書ける
print(T.pivot_table("survived", index="sex", columns="age"))

print(titanic.groupby("sex")[["survived"]].mean())
print(titanic.groupby("sex")["survived"].mean())
print(titanic.groupby(["sex", "class"])["survived"].aggregate("mean").unstack())

print(titanic.pivot_table("survived", index="sex", columns="class"))
print(titanic.pivot_table("survived", index="sex", columns="class", aggfunc="mean", margins=True))
print(titanic.pivot_table(index="sex", columns="class", aggfunc={"survived": "sum", "fare": "mean"}))

そのほかの機能

Notebook の最後では、Pandas の応用的な機能がいくつか紹介されています。ここでは見出しだけ出ているものと、実際にコードがあるものを分けて整理します。

文字列操作と時系列処理

この Notebook では「文字列のベクトル化処理」と「時系列処理」という見出しのみが出ており、詳細なコードは続いていません。ただし、Pandas には .str アクセサや日時型の処理機能があり、文字列列や時系列列を一括で扱えるのが大きな特徴です。

多重インデックス

多重インデックスを使うと、都市 x 年 のような二段階のラベルを持つ表を自然に扱えます。複数軸を持つデータを表形式で管理したいときに便利です。

import numpy as np
import pandas as pd

# 都市と年の 2 段階 index を持つ表を作る
base_data = np.array([
    [3200, 180000],  # 東京 2008
    [3340, 200000],  # 東京 2018
    [1900, 60000],   # 大阪 2008
    [1950, 70000],   # 大阪 2018
    [360, 12000],    # 横浜 2008
    [370, 13000],    # 横浜 2018
    [700, 50000],    # 名古屋 2008
    [720, 60000],    # 名古屋 2018
])

data = pd.DataFrame(
	base_data,
	index=[
		["東京", "東京", "大阪", "大阪", "横浜", "横浜", "名古屋", "名古屋"],
		[2008, 2018] * 4,
	],
	columns=["population", "GDP"],
)

print(data)
# index 名を付けると意味が分かりやすい
data.index.names = ["city", "year"]
print(data)

# 1 段目だけ / 2 段目まで指定して取り出す
print(data["GDP"])
print(data.loc["大阪", "GDP"])
print(data.loc["大阪", 2018]["GDP"])

eval() と query() による高速化

複雑な式や条件抽出を大量データに対して行うとき、eval()query() が有効になることがあります。中間配列の生成を減らせるためです。ただし、小さなデータでは普通の書き方の方が速いこともあります。

import numpy as np
import pandas as pd

df1, df2, df3, df4 = (pd.DataFrame(np.random.random((10000, 100))) for _ in range(4))

# 通常の計算式と pd.eval() の速度を比べる
%timeit (df1 + df2) / (df3 + df4)
%timeit pd.eval("(df1+df2)/(df3+df4)")

# 計算結果が一致するか確認する
print(np.allclose((df1 + df2) / (df3 + df4), pd.eval("(df1+df2)/(df3+df4)")))

eval() は列同士の式を DataFrame 内で評価するのにも使えます。@変数名 を使うとローカル変数も参照できます。

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.random((1000, 3)), columns=list("ABC"))
print(df.head())

# 外部から式を渡して評価する
res_1 = pd.eval("(df.A+df.B)/(df.C-1)")
# DataFrame メソッドとしても書ける
res_2 = df.eval("(A+B)/(C-1)")
print(np.allclose(res_1, res_2))

# 新しい列を計算式から追加する
df["D"] = pd.eval("(df.A+df.B)/(df.C-1)")
print(df.head())

# inplace=True なら元の DataFrame を直接更新する
df.eval("D=(A+B)/(C-1)", inplace=True)
print(df.head())

# @変数名 でローカル変数も参照できる
column_mean = df.mean(axis=1)
res = df.eval("A+@column_mean")
print(res.head())

query() は条件抽出を読みやすく書けるのが利点です。条件式が長くなると、特に見通しがよくなります。

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.random((1000, 3)), columns=list("ABC"))
print(df.head())

# 通常の条件式と query() の速度を比べる
%timeit df[(df.A < 0.5) & (df.B > 0.5)]
%timeit df.query("(A < 0.5) & (B > 0.5)")

# query() の結果をそのまま確認する
print(df.query("(A < 0.5) & (B > 0.5)").head())
print(np.allclose(df[(df.A < 0.5) & (df.B > 0.5)], df.query("(A < 0.5) & (B > 0.5)")))

# メモリ使用量の比較用
print(df.values.nbytes)
print(df1.values.nbytes)

まとめ

Pandas を最初に学ぶときは、関数名を単発で覚えるよりも、次の流れでつなげて覚えると理解しやすくなります。

  1. SeriesDataFrame を作り、Pandas の基本的な器を理解する。
  2. indexcolumnslociloc で表を読む。
  3. head()info()、統計量でデータの全体像を確認する。
  4. isnull()dropna()fillna() で欠損値を扱う。
  5. concat()merge() で表を結合する。
  6. groupby()pivot_table() で集計表を作る。
  7. 必要に応じて eval()query() で式を簡潔かつ効率的に書く。

この流れで何度か手を動かすと、CSV の読み込みから前処理、集計、可視化前の整形までを、Pandas を軸に自然につなげられるようになります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?