Skip to content

5. データ操作の基礎 - pandas

第 5 回では pandas を用いたデータ操作について学びます。

5.1 pandas の概要

pandas は、Python でデータ操作やデータ分析を行うためのライブラリで、NumPy を基盤として構築されており、NumPy よりも柔軟にデータを扱えるように設計されています。

pandas が提供する主要なデータ構造には、Series と DataFrame があります。

5.1.1 pandas のインポート

pandas は以下のようにインポートして使います。

import pandas as pd

5.2 Series

Series(シリーズ)は、一次元のラベル付きデータを格納することができます。リストや配列と似ていますが、インデックス(ラベル)を使ってアクセスできる特徴があります。

Series は以下のように、pandas.Series() 関数で作ることができます。

リストから Series の作成
import pandas as pd

data = [10, 20, 30]
s = pd.Series(data)
print(s)
Output
0    10
1    20
2    30
dtype: int64

インデックスを指定して Series を作成するには以下のようにします。

インデックスを指定した Series の作成
s = pd.Series([10, 20, 30], index=["a", "b", "c"])
print(s)
Output
a    10
b    20
c    30
dtype: int64

以下のように辞書から Series を作成することもできます。

辞書から Series の作成
data = {"apple": 100, "banana": 200, "orange": 300}
s = pd.Series(data)
print(s)
Output
apple     100
banana    200
orange    300
dtype: int64

値にアクセスする際は、ブラケット ([]) を使う方法と、pandas.Series.at() 関数を使う方法があります。

  • [] はスライス操作などができる柔軟性を持ちます。
  • at は要素へのアクセスに特化しているため高速に動作します。
Series の値へのアクセス
print(s["banana"])      # 出力: 200
print(s.at["banana"])   # 出力: 200

Series は内部で NumPy 配列を利用しているため、NumPy のブロードキャストと同様の演算操作が可能です。例えば、各要素に対して以下のように演算を行うことができます。

Series の要素に対する演算(消費税計算)
s = s * 1.08
print(s)
Output
apple     108.0
banana    216.0
orange    324.0
dtype: float64

条件式による要素抽出も可能です。

Series における要素の抽出
print(s[s > 200])
Output
banana    216.0
orange    324.0
dtype: float64

5.3 DataFrame

DataFrame(データフレーム)は、複数の Series を列にまとめた二次元の表形式のデータ構造です。DataFrame は Excel や SQL などの多くの表形式のデータとの連携が容易であること、実務データは複数の変数(列)を持つことが一般的であること、Series を包含していることなどから、実務では圧倒的に DataFrame が使用されることが多いです。

5.3.1 DataFrame の作り方

DataFrame を作るためには、pandas.DataFrame() 関数を使用します。

以下は、3 人分の「名前」「年齢」「身長」の情報を含む二次元のリストから DataFrame を作成する例です。columns で列名を指定することができ、これを省略すると整数のインデックス(0, 1, 2, ...)が列名として使用されます。

リストから DataFrame の作成
import pandas as pd

data = [
    ["太郎", 25, 170],
    ["花子", 30, 160],
    ["次郎", 22, 175]
]

df = pd.DataFrame(data, columns=["名前", "年齢", "身長"])
print(df)
Output
   名前  年齢   身長
0  太郎   25   170
1  花子   30   160
2  次郎   22   175

デフォルトでは各行に整数のインデックス(0, 1, 2, ...)が振られますが、Series と同様に任意のインデックス(ラベル)を指定することもできます。

リストから DataFrame の作成(インデックスを指定)
import pandas as pd

data = [
    ["太郎", 25, 170],
    ["花子", 30, 160],
    ["次郎", 22, 175]
]

df = pd.DataFrame(data, columns=["名前", "年齢", "身長"], index=["A", "B", "C"])
print(df)
Output
   名前  年齢   身長
A  太郎   25   170
B  花子   30   160
C  次郎   22   175

以下のように、辞書から DataFrame を作成することもできます。

辞書から DataFrame の作成
import pandas as pd

data = {
    "名前": ["太郎", "花子", "次郎"],
    "年齢": [25, 30, 22],
    "身長": [170, 160, 175]
}

df = pd.DataFrame(data)
print(df)
Output
   名前  年齢   身長
0  太郎   25   170
1  花子   30   160
2  次郎   22   175

5.3.2 DataFrame の読み込み

DataFrame はリストや辞書から作成する以外に、外部から読み込ませて扱うケースも多くあります。

サンプルデータセットの利用

インターネット上に存在する多くのサンプルデータセットは、DataFrame の形式で提供されています。例えば、第 3 回の「データ可視化」で扱った Seaborn のサンプルデータセットも DataFrame 形式であり、以下のようにして読み込ませることができます。

Seaborn の penguins データセットの読み込み
import seaborn as sns
penguins = sns.load_dataset("penguins")
print(penguins.head())
Output
  species     island  bill_length_mm  ...  flipper_length_mm  body_mass_g     sex
0  Adelie  Torgersen            39.1  ...              181.0       3750.0    Male
1  Adelie  Torgersen            39.5  ...              186.0       3800.0  Female
2  Adelie  Torgersen            40.3  ...              195.0       3250.0  Female
3  Adelie  Torgersen             NaN  ...                NaN          NaN     NaN
4  Adelie  Torgersen            36.7  ...              193.0       3450.0  Female

外部ファイルの読み込み

pandas.read_csv() 関数や pandas.read_excel() 関数を使うことで、CSV ファイル Excel ファイルを DataFrame として読み込ませることができます。

CSV ファイルの読み込み
import pandas as pd
df = pd.read_csv("data.csv")
print(df.head())
Excel ファイルの読み込み
import pandas as pd
df = pd.read_excel("data.xlsx")
print(df.head())

pandas.read_excel() 関数では、sheet_name パラメータで読み込むシート名を、usecols パラメータで読み込む列を指定することも可能です。

CSV ファイル

CSV(Comma-Separated Values)は、データをカンマ(,)で区切ったテキスト形式のファイルです。CSV は Excel で開くことが多いかもしれませんが、中身は単純なテキストデータなので、メモ帳などで開くこともできます。

5.3.3 DataFrame の操作

DataFrame を操作する方法を見ていきましょう。df という名前で以下の DataFrame があるものとします。

DataFrame
   名前  年齢   身長
0  太郎   25   170
1  花子   30   160
2  次郎   22   175

並び替え(ソート)

DataFrame の任意の列を並べ替えたい(ソートしたい)場合は、DataFrame.sort_values() 関数を用います。並び替えの対象とする列は by= パラメータで指定します。

年齢の低い順(昇順)にソート
sorted_df = df.sort_values(by="年齢")
print(sorted_df)
Output
   名前  年齢   身長
2  次郎  22  175
0  太郎  25  170
1  花子  30  160

デフォルトでは上の例のように、数字の小さい順(昇順)での並び替えが行われます。数字の大きい順(降順)での並び替えをしたい場合は、以下のように ascending=False パラメータを追加します。

年齢の高い順(降順)にソート
sorted_df = df.sort_values(by="年齢", ascending=False)
print(sorted_df)
Output
   名前  年齢   身長
1  花子  30  160
0  太郎  25  170
2  次郎  22  175

列の抽出

DataFrame から列を抽出したい場合は、以下のように列名を指定します。このとき、一次元の Series データが抽出されます。

DataFrame から列の抽出
print(df["名前"])
Output
0    太郎
1    花子
2    次郎
Name: 名前, dtype: object

列名をリストで指定することで、複数の列を同時に抽出することもできます。このとき、抽出したデータも DataFrame となります。

DataFrame から複数列の抽出
print(df[["年齢", "身長"]])
Output
   年齢   身長
0   25   170
1   30   160
2   22   175

行の抽出

DataFrame から行を抽出したい場合は、pandas.loc[] インデクサまたは pandas.iloc[] インデクサを使用します。loc ではインデックス名を指定するのに対し、iloc では整数インデックスを指定します。

DataFrame から行の抽出(iloc も同様)
print(df.loc[1])
Output
名前     花子
年齢      30
身長     160
Name: 1, dtype: object

複数の行を抽出するには、リストで指定するか、スライスを使用します。

DataFrame から複数行の抽出(リストで指定)
print(df.loc[[0, 1]])
DataFrame から複数行の抽出(スライスを使用)
print(df.iloc[:2])
Output
   名前  年齢   身長
0  太郎   25   170
1  花子   30   160

デフォルトの整数インデックスの場合、loc と iloc は同じように使うことができます。ただし、スライスを使用する場合、iloc はリストのスライスと同様に終端を含まない範囲を抽出する一方で、loc では終端を含む点に注意が必要です。上のスライスを使用する例で iloc を loc に置き換える場合は、df.loc[:1] となります。

行・列の抽出

df.loc[行ラベル, 列ラベル] とすることで、行と列を同時に指定して抽出することもできます。すべての行と列を取得するには、以下のようにします。

DataFrame からすべての行と列を抽出
print(df.loc[:, :])

行ラベル(インデックス)が 1 、列ラベルが "名前" の値を抽出したい場合は以下のようにします。

DataFrame から行と列を指定して抽出
print(df.loc[1, "名前"])
Output
花子

行ラベル(インデックス)が 1 以降、列ラベルが "名前" と "年齢" の値を抽出したい場合は以下のようにします。

DataFrame から行と列を指定して抽出
print(df.loc[1:, ["名前", "年齢"]])
Output
   名前  年齢
1  花子   30
2  次郎   22

条件式による抽出

条件式を用いることで、DataFrame から効率的にデータを抽出することが可能になります。

  • 条件式には、==, !=, <, >, <=, >= などの比較演算子を使用します。
  • 複数の条件を指定する場合は、& (AND), | (OR), ~ (NOT) などの論理演算子を使用します。

例えば、年齢が 25 歳以上の人を抽出するには、以下のようにします。

年齢が25歳以上の人を抽出
print(df.loc[df["年齢"] >= 25])
Output
   名前  年齢   身長
0  太郎   25   170
1  花子   30   160

数値だけでなく、文字列の一致(==)や不一致(!=)による抽出も同じ書き方で行えます。例えば、名前が「太郎」の行を抽出するには以下のようにします。

文字列による抽出
print(df.loc[df["名前"] == "太郎"])
Output
   名前  年齢   身長
0  太郎   25   170

年齢が 25 歳以上かつ身長が 170 cm 未満の人を抽出するには、以下のようにします。

年齢が25歳以上かつ身長が170cm未満の人を抽出
print(df.loc[(df["年齢"] >= 25) & (df["身長"] < 170)])
Output
   名前  年齢   身長
1  花子   30   160

複数条件を利用する際の注意

DataFrame におけるデータ抽出において、& (AND) や | (OR) を用いて複数の条件を指定する際は、それぞれの条件式を括弧 () で囲う必要がある点に注意してください。

✅️ df.loc[(df["年齢"] >= 25) & (df["身長"] < 170)]

❌️ df.loc[df["年齢"] >= 25 & df["身長"] < 170]

これらの抽出方法をうまく組み合わせることで、複雑な条件でも効率的にデータ抽出をすることができるようになります。

5.3.4 列の追加・削除

列の追加

DataFrame に新しい列を追加するには、df["新しい列名"] = ... のように代入します。既存の列を使った計算結果や、行数の揃ったリストを渡すことができます。

列の追加
# 既存の列から計算した新しい列
df["5年後の年齢"] = df["年齢"] + 5

# リストから新しい列
df["体重"] = [60, 50, 70]

# 複数の列を使った計算(BMI = 体重 / (身長 m)²)
df["BMI"] = df["体重"] / (df["身長"] / 100) ** 2

print(df)
Output
   名前  年齢   身長  5年後の年齢  体重        BMI
0  太郎   25   170          30   60  20.761246
1  花子   30   160          35   50  19.531250
2  次郎   22   175          27   70  22.857143

列の削除

不要になった列を削除するには、DataFrame.drop() 関数を用い、columns= 引数で削除したい列名を指定します。複数の列を削除したい場合は、リストで指定します。

列の削除
# 単一の列を削除
df = df.drop(columns=["5年後の年齢"])

# 複数の列を削除
df = df.drop(columns=["体重", "BMI"])

print(df)
Output
   名前  年齢   身長
0  太郎   25   170
1  花子   30   160
2  次郎   22   175

drop() 関数は、fillna() などと同様、呼び出しただけでは元の DataFrame は変更されません。結果を反映させたい場合は、上の例のように再代入を行ってください。

5.3.5 集計と groupby

集計関数

DataFrame や Series に対して、合計や平均などの集計を行う関数が用意されています。代表的なものは、第 4 回の NumPy で扱ったものと同じ名前です。

関数 説明
sum() 合計
mean() 平均
std() 標準偏差
min() 最小値
max() 最大値
median() 中央値

特定の列に対して集計を行うには、Series として取り出してから関数を呼び出します。

特定の列の集計
print("平均年齢:", df["年齢"].mean())
print("最大身長:", df["身長"].max())
Output
平均年齢: 25.666666666666668
最大身長: 175

DataFrame 全体に対して mean() などを呼び出すと、各列の集計値が一度に得られます。文字列の列が混ざっている場合は、引数で numeric_only=True を指定すると、数値列のみが対象になります。

数値列ごとの平均
print(df.mean(numeric_only=True))
Output
年齢     25.666667
身長    168.333333
dtype: float64

groupby によるカテゴリ別集計

「カテゴリごとに平均を見たい」「グループごとに合計を出したい」といった場合は、DataFrame.groupby() 関数を使います。引数にカテゴリ列の名前を渡すと、その値ごとにデータがまとめられ、続けて集計関数を呼び出すと、グループ別の集計結果が得られます。

例えば、「クラス」の列を追加してクラスごとの平均を計算するには、以下のようにします。

クラス別の平均
df["クラス"] = ["A", "B", "A"]
print(df.groupby("クラス").mean(numeric_only=True))
Output
        年齢     身長
クラス
A      23.5  172.5
B      30.0  160.0

groupby() はデータ分析で非常に頻繁に使われる操作です。複雑な集計や、カテゴリと数値を組み合わせた分析の入り口として、しっかり押さえておきましょう。

演習

演習 5-1:データの抽出とソート

以下の学生 5 人の試験結果データから DataFrame を作成し、次の処理を行ってください。

  1. 国語が 80 点以上の学生を抽出して表示する
  2. 数学の点数で降順にソートして表示する
テンプレートコード
import pandas as pd

data = {
    "名前": ["茨城太郎", "水戸花子", "日立次郎", "つくば梅子", "鹿島三郎"],
    "国語": [85, 92, 65, 78, 88],
    "数学": [70, 85, 90, 60, 75],
    "英語": [80, 75, 70, 95, 65],
}
df = pd.DataFrame(data)

# 1. 国語が 80 点以上の学生を抽出

# 2. 数学の点数で降順にソート

演習 5-2:列の追加と集計

演習 5-1 と同じ試験結果データを使い、次の処理を行ってください。

  1. 3 教科の点数を合計した「合計」列を追加する
  2. 合計を 3 で割った「平均」列を追加する
  3. 全学生の「数学」の平均点を計算して表示する
テンプレートコード
import pandas as pd

data = {
    "名前": ["茨城太郎", "水戸花子", "日立次郎", "つくば梅子", "鹿島三郎"],
    "国語": [85, 92, 65, 78, 88],
    "数学": [70, 85, 90, 60, 75],
    "英語": [80, 75, 70, 95, 65],
}
df = pd.DataFrame(data)

# 1. 「合計」列を追加

# 2. 「平均」列を追加

print(df)

# 3. 数学の平均点を計算して表示

演習 5-3:曜日別の平均チップ額の可視化

Seaborn の tips データセットを使い、次の処理を行ってください。

  1. データセットを読み込む
  2. 曜日 (day) ごとの平均チップ額 (tip) を計算する
  3. 結果を棒グラフで可視化する(X 軸:曜日、Y 軸:平均チップ額)
テンプレートコード
import seaborn as sns
import matplotlib.pyplot as plt

# tips データセットの読み込み
tips = sns.load_dataset("tips")

# 1. 曜日ごとの平均チップ額を計算(groupby)

# 2. 棒グラフで可視化

plt.tight_layout()
plt.show()

演習 5-4:カラット区分ごとの価格分布

Seaborn の diamonds データセットを使い、ダイヤモンドのカラット (carat) を以下の 4 区分に分けて、各区分の「価格 (price) の分布」を 2×2 のサブプロットでヒストグラムとして描画してください。

  • 区分 1: 0 ≤ carat < 0.5
  • 区分 2: 0.5 ≤ carat < 1
  • 区分 3: 1 ≤ carat < 2
  • 区分 4: carat ≥ 2

各サブプロットにはカラット区分がわかるタイトルも設定してください。

テンプレートコード
import seaborn as sns
import matplotlib.pyplot as plt

# diamonds データセットの読み込み
diamonds = sns.load_dataset("diamonds")

# 4 つのカラット区分でデータを抽出(条件抽出)

# 2x2 のサブプロットを作成し、各区分の price のヒストグラムを描画

plt.tight_layout()
plt.show()

課題

課題5:レストランのチップデータ分析

Seaborn の tips データセットを使い、以下の手順をすべて行う Python コードを作成し、コメントで考察してください。

  1. tips データセットを読み込む
  2. 1 人あたりの支払額を表す per_person 列を追加する(total_bill / size)
  3. チップの割合(%)を表す tip_rate 列を追加する(tip / total_bill * 100)
  4. 合計金額(total_bill)が 20 ドル以上の会計のみを抽出する
  5. 抽出後のデータについて、曜日(day)ごとに「tip_rate の平均」と「per_person の平均」を groupby で集計する
  6. 集計結果を「tip_rate の平均」が高い順に並べ替えて表示する
  7. 得られた結果に対し、「なぜそのようになったか」「レストラン経営にどう活用できるか」を考察する (発展)必要に応じて考察を補強するための可視化を行う