5. データ操作の基礎 - pandas¶
第 5 回では pandas を用いたデータ操作について学びます。
5.1 pandas の概要¶
pandas は、Python でデータ操作やデータ分析を行うためのライブラリで、NumPy を基盤として構築されており、NumPy よりも柔軟にデータを扱えるように設計されています。
pandas が提供する主要なデータ構造には、Series と DataFrame があります。
5.1.1 pandas のインポート¶
pandas は以下のようにインポートして使います。
5.2 Series¶
Series(シリーズ)は、一次元のラベル付きデータを格納することができます。リストや配列と似ていますが、インデックス(ラベル)を使ってアクセスできる特徴があります。
Series は以下のように、pandas.Series() 関数で作ることができます。
インデックスを指定して Series を作成するには以下のようにします。
以下のように辞書から Series を作成することもできます。
値にアクセスする際は、ブラケット ([]) を使う方法と、pandas.Series.at() 関数を使う方法があります。
[]はスライス操作などができる柔軟性を持ちます。atは要素へのアクセスに特化しているため高速に動作します。
Series は内部で NumPy 配列を利用しているため、NumPy のブロードキャストと同様の演算操作が可能です。例えば、各要素に対して以下のように演算を行うことができます。
条件式による要素抽出も可能です。
5.3 DataFrame¶
DataFrame(データフレーム)は、複数の Series を列にまとめた二次元の表形式のデータ構造です。DataFrame は Excel や SQL などの多くの表形式のデータとの連携が容易であること、実務データは複数の変数(列)を持つことが一般的であること、Series を包含していることなどから、実務では圧倒的に DataFrame が使用されることが多いです。
5.3.1 DataFrame の作り方¶
DataFrame を作るためには、pandas.DataFrame() 関数を使用します。
以下は、3 人分の「名前」「年齢」「身長」の情報を含む二次元のリストから DataFrame を作成する例です。columns で列名を指定することができ、これを省略すると整数のインデックス(0, 1, 2, ...)が列名として使用されます。
import pandas as pd
data = [
["太郎", 25, 170],
["花子", 30, 160],
["次郎", 22, 175]
]
df = pd.DataFrame(data, columns=["名前", "年齢", "身長"])
print(df)
デフォルトでは各行に整数のインデックス(0, 1, 2, ...)が振られますが、Series と同様に任意のインデックス(ラベル)を指定することもできます。
import pandas as pd
data = [
["太郎", 25, 170],
["花子", 30, 160],
["次郎", 22, 175]
]
df = pd.DataFrame(data, columns=["名前", "年齢", "身長"], index=["A", "B", "C"])
print(df)
以下のように、辞書から DataFrame を作成することもできます。
import pandas as pd
data = {
"名前": ["太郎", "花子", "次郎"],
"年齢": [25, 30, 22],
"身長": [170, 160, 175]
}
df = pd.DataFrame(data)
print(df)
5.3.2 DataFrame の読み込み¶
DataFrame はリストや辞書から作成する以外に、外部から読み込ませて扱うケースも多くあります。
サンプルデータセットの利用¶
インターネット上に存在する多くのサンプルデータセットは、DataFrame の形式で提供されています。例えば、第 3 回の「データ可視化」で扱った Seaborn のサンプルデータセットも DataFrame 形式であり、以下のようにして読み込ませることができます。
import seaborn as sns
penguins = sns.load_dataset("penguins")
print(penguins.head())
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 として読み込ませることができます。
pandas.read_excel() 関数では、sheet_name パラメータで読み込むシート名を、usecols パラメータで読み込む列を指定することも可能です。
CSV ファイル
CSV(Comma-Separated Values)は、データをカンマ(,)で区切ったテキスト形式のファイルです。CSV は Excel で開くことが多いかもしれませんが、中身は単純なテキストデータなので、メモ帳などで開くこともできます。
5.3.3 DataFrame の操作¶
DataFrame を操作する方法を見ていきましょう。df という名前で以下の DataFrame があるものとします。
並び替え(ソート)¶
DataFrame の任意の列を並べ替えたい(ソートしたい)場合は、DataFrame.sort_values() 関数を用います。並び替えの対象とする列は by= パラメータで指定します。
デフォルトでは上の例のように、数字の小さい順(昇順)での並び替えが行われます。数字の大きい順(降順)での並び替えをしたい場合は、以下のように ascending=False パラメータを追加します。
列の抽出¶
DataFrame から列を抽出したい場合は、以下のように列名を指定します。このとき、一次元の Series データが抽出されます。
列名をリストで指定することで、複数の列を同時に抽出することもできます。このとき、抽出したデータも DataFrame となります。
行の抽出¶
DataFrame から行を抽出したい場合は、pandas.loc[] インデクサまたは pandas.iloc[] インデクサを使用します。loc ではインデックス名を指定するのに対し、iloc では整数インデックスを指定します。
複数の行を抽出するには、リストで指定するか、スライスを使用します。
デフォルトの整数インデックスの場合、loc と iloc は同じように使うことができます。ただし、スライスを使用する場合、iloc はリストのスライスと同様に終端を含まない範囲を抽出する一方で、loc では終端を含む点に注意が必要です。上のスライスを使用する例で iloc を loc に置き換える場合は、df.loc[:1] となります。
行・列の抽出¶
df.loc[行ラベル, 列ラベル] とすることで、行と列を同時に指定して抽出することもできます。すべての行と列を取得するには、以下のようにします。
行ラベル(インデックス)が 1 、列ラベルが "名前" の値を抽出したい場合は以下のようにします。
行ラベル(インデックス)が 1 以降、列ラベルが "名前" と "年齢" の値を抽出したい場合は以下のようにします。
条件式による抽出¶
条件式を用いることで、DataFrame から効率的にデータを抽出することが可能になります。
- 条件式には、
==,!=,<,>,<=,>=などの比較演算子を使用します。 - 複数の条件を指定する場合は、
&(AND),|(OR),~(NOT) などの論理演算子を使用します。
例えば、年齢が 25 歳以上の人を抽出するには、以下のようにします。
数値だけでなく、文字列の一致(==)や不一致(!=)による抽出も同じ書き方で行えます。例えば、名前が「太郎」の行を抽出するには以下のようにします。
年齢が 25 歳以上かつ身長が 170 cm 未満の人を抽出するには、以下のようにします。
複数条件を利用する際の注意
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)
名前 年齢 身長 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)
drop() 関数は、fillna() などと同様、呼び出しただけでは元の DataFrame は変更されません。結果を反映させたい場合は、上の例のように再代入を行ってください。
5.3.5 集計と groupby¶
集計関数¶
DataFrame や Series に対して、合計や平均などの集計を行う関数が用意されています。代表的なものは、第 4 回の NumPy で扱ったものと同じ名前です。
| 関数 | 説明 |
|---|---|
sum() |
合計 |
mean() |
平均 |
std() |
標準偏差 |
min() |
最小値 |
max() |
最大値 |
median() |
中央値 |
特定の列に対して集計を行うには、Series として取り出してから関数を呼び出します。
DataFrame 全体に対して mean() などを呼び出すと、各列の集計値が一度に得られます。文字列の列が混ざっている場合は、引数で numeric_only=True を指定すると、数値列のみが対象になります。
groupby によるカテゴリ別集計¶
「カテゴリごとに平均を見たい」「グループごとに合計を出したい」といった場合は、DataFrame.groupby() 関数を使います。引数にカテゴリ列の名前を渡すと、その値ごとにデータがまとめられ、続けて集計関数を呼び出すと、グループ別の集計結果が得られます。
例えば、「クラス」の列を追加してクラスごとの平均を計算するには、以下のようにします。
groupby() はデータ分析で非常に頻繁に使われる操作です。複雑な集計や、カテゴリと数値を組み合わせた分析の入り口として、しっかり押さえておきましょう。
演習¶
演習 5-1:データの抽出とソート
以下の学生 5 人の試験結果データから DataFrame を作成し、次の処理を行ってください。
- 国語が 80 点以上の学生を抽出して表示する
- 数学の点数で降順にソートして表示する
演習 5-2:列の追加と集計
演習 5-1 と同じ試験結果データを使い、次の処理を行ってください。
- 3 教科の点数を合計した「合計」列を追加する
- 合計を 3 で割った「平均」列を追加する
- 全学生の「数学」の平均点を計算して表示する
演習 5-3:曜日別の平均チップ額の可視化
Seaborn の tips データセットを使い、次の処理を行ってください。
- データセットを読み込む
- 曜日 (
day) ごとの平均チップ額 (tip) を計算する - 結果を棒グラフで可視化する(X 軸:曜日、Y 軸:平均チップ額)
演習 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
各サブプロットにはカラット区分がわかるタイトルも設定してください。
課題¶
課題5:レストランのチップデータ分析
Seaborn の tips データセットを使い、以下の手順をすべて行う Python コードを作成し、コメントで考察してください。
tipsデータセットを読み込む- 1 人あたりの支払額を表す
per_person列を追加する(total_bill / size) - チップの割合(%)を表す
tip_rate列を追加する(tip / total_bill * 100) - 合計金額(
total_bill)が 20 ドル以上の会計のみを抽出する - 抽出後のデータについて、曜日(
day)ごとに「tip_rateの平均」と「per_personの平均」をgroupbyで集計する - 集計結果を「
tip_rateの平均」が高い順に並べ替えて表示する - 得られた結果に対し、「なぜそのようになったか」「レストラン経営にどう活用できるか」を考察する (発展)必要に応じて考察を補強するための可視化を行う