pandasに入門

モリ(エンジニア)モリ(エンジニア)
2024.08.30

IT技術

こんにちは!

最近データエンジニア的なお仕事をしています。

データの比較をするときなどにpandasを使っているのですが、雰囲気で使ってしまっている感が否めません。

このままではいかん!!!

ということで今回は、公式サイトのチュートリアルを使ってpandasにしっかりと入門していこうと思います!

※本記事は筆者が上記公式のチュートリアルをやりながら勉強用にメモしたものなので、飛ばしている箇所や、誤りや不足がある可能性がございます。ご了承ください。

 

環境

  • Mac
  • JupyterLab

 

データの種類について

pandasを利用するには次のようにインポートをします。

1import pandas as pd

pandasの1つのデータ構造にDataFrameというものがあります。

DataFrameは、文字型や数値型などさまざまなデータを格納できる2次元のデータ構造で、行と列で表現されます。スプレッドシートやSQLのテーブルによく似たものと考えればOKです。

ここでは、例として「タイタニック号の乗客データ」をもつDataFrameを作成していきます。乗客データには、名前(characters)、年齢(integers)、性別(male/female)のデータを持たせるようにします。

次のようにしてDataFrameを作成することができます。

1df = pd.DataFrame(
2    {
3        "Name": [
4            "Braund, Mr. Owen Harris",
5            "Allen, Mr. William Henry",
6            "Bonnell, Miss. Elizabeth",
7        ],
8        "Age": [22, 35, 58],
9        "Sex": ["male", "male", "female"],
10    }
11)

下の画像のような「Name」,「Age」,「Sex」という列をもつDataFrameが作成されていることがわかります。

pandasではDataFrameの各列のことをSeriesと呼びます。

Seriesを取得する場合は次のように書きます。(Age列を取得する場合)

1df["Age"]

Seriesを作成するには次のように書きます。

1ages = pd.Series([22, 35, 58], name="Age")

agesを確認すると下の画像のようにSeriesが作成されているのがわかります。

SeriesはDataFrameの1列なので列のラベルはありません。

 

では、上記で作成したDataFrameやSeriesを使って色々表示させてみます。

乗客の中で一番高い年齢を取得する場合、max()メソッドを使って次のように書きます。

DataFrame

1df["Age"].max()

Series

1ages.max()

各列の平均値や標準偏差・最大値・最小値・最頻値などの要約統計量を取得したい場合は、describe()メソッドを使って次のように書きます。

1df.describe()

※「Name」列と「Sex」列はテキストデータなため、デフォルトではdescribe()メソッドでは考慮されません。

 

表形式の読み書き方法

※ここからこちらのページの「Titanic data」を利用します。

CSVデータを分析したい場合は、read_csv()関数を使って次のように書きます。

1titanic = pd.read_csv("data/titanic.csv")

「titanic」を確認すると下の画像のように、デフォルトでは最初と最後の5行が表示されます。

例えば、最初の8行を確認したい場合は、head()メソッドを使って次のように書きます。

1titanic.head(8)

head()メソッドの引数に表示したい行数の数値を指定してあげればOKです!

※「最後のN行」、を表示したい場合は、tail()メソッドを使用します。

 

pandasで各列のデータ型を確認したいときは、dtypes属性を使って次のように書きます。

1titanic.dtypes

※上記DataFrame のデータ型は、整数 (int64)、浮動小数点数 (float64)、および文字列 (object) です。

 

CSVデータをExcelファイルとして取得したい場合は、to_excel()メソッドを使って次のように書きます。

1titanic.to_excel("titanic.xlsx", sheet_name="passengers", index=False)

第二引数のsheet_nameでシートに名前をつけることができます。(デフォルトは「Sheet1」)
第三引数でIndex=Falseと設定すると、行インデックスのラベルはExcelファイルに保存されません。

 

続いて、上記で作ったExcelファイルを読み込みたいと思います!

read_excel()メソッドを使って次のように書きます。

1titanic = pd.read_excel("titanic.xlsx", sheet_name="passengers")

データの概要を見たいときは、info()メソッドを使って次のように書きます。

1titanic.info()


表示される情報は下記のとおりです。

  • データ構造
  • 行数
  • 列数
  • 列名
  • 各列のデータ型
  • メモリ使用量

 

DataFrameのサブセットの取得方法

特定の列の取得方法

例えば、Age列を選択したい場合は、次のように書きます。

1ages = titanic["Age"]

続いて、Age列とSex列を取得したい場合は、次のように書きます。

1age_sex = titanic[["Age", "Sex"]]

 

特定の行の取得方法

例えば、35歳以上の乗客を取得するには次のように書きます。

1above_35 = titanic[titanic["Age"] > 35]

「titanic["Age"] > 35」と書くことで、下の画像のようにAge列の値が35より大きいかどうかをチェックして、booleanの値を返します。

そして、値がTrueの行のみを取得するようになります。

titanicのDataFrameが891行に対して、上記でフィルタしたDataFrameのabove_35が何行になっているかも確認すると下の画像のように217行にフィルタされているのがわかります。

 

続いて、Cabinクラス(Pclass列)が「2」と「3」の乗客を取得してみます。
isin()条件関数を使って次のように書きます。

1class_23 = titanic[titanic["Pclass"].isin([2, 3])]

isin()の引数のリストの値に含まれる各行にTrueを返します。

上記のisin()関数でのフィルタリングは次のように書くこともできます。

1class_23 = titanic[(titanic["Pclass"] == 2) | (titanic["Pclass"] == 3)]

 

続いて、年齢がわかっている乗客を取得してみます。
notna()条件関数を使って次のように書きます。

1age_no_na = titanic[titanic["Age"].notna()]

notna()は値がNullではない場合にTrueを返します。
shapeを使って確認すると714行と出力されているのでフィルタリングされているようです。

 

特定の行と列の取得方法

例えば、35歳以上の乗客の名前を取得するにはloc演算子やiloc演算子を使って次のように書きます。

1adult_names = titanic.loc[titanic["Age"] > 35, "Name"]

loc演算子とiloc演算子のカンマの前が行、後ろが列に関して書きます。

また、カンマの前後には下記を設定することが可能です。

  • 1つのラベル(行名や列名)
  • 複数のラベル
  • スライスしたラベル
  • 条件式
  • コロン(全ての行または列を選択)

 

続いて、10〜25行目と3〜5列目を取得してみます。

iloc演算子を使って次のように書きます。

1titanic.iloc[9:25, 2:5]

上記のように、テーブル内のindexに基づいて特定の行や列を取得する場合は、iloc演算子を使用します。

locやiloc演算子を使うことで取得だけではなく、新しい値を割り当てることもできます。
例えば、「anonymous」という名前を4列目の最初の3つの要素に割り当てるには次のように書きます。

1titanic.iloc[0:3, 3] = "anonymous"

 

プロットの作成方法

※ここからこちらのページの「Air quality data」を利用します。

まず、プロットを作成するために次のようにmatplotlibパッケージのpyplotモジュールをimportします。

1import matplotlib.pyplot as plt

また、「Air qulity data」のcsvファイルを次のように読み込んでおきます。

1air_quality = pd.read_csv("data/air_quality_no2.csv", index_col=0, parse_dates=True)

※上記read_csv()メソッドのindex_colとparse_datesパラメータを使用して、最初の列(0番目)をDataFrameのインデックスとし、列の日付をTimestampオブジェクトに変換しています

ここまででプロットを表示させる準備は完了です!

まずは、簡単にプロットさせる方法を見ていきます。
plot()メソッドを使って次のように書きます。

1air_quality.plot()

※チュートリアル内ではair_quality.plot()と書くだけではグラフは表示されず、「plt.show()」というのも書いていますが、自分が試したときは上記だけでグラフが表示されました

DataFrameで使用するとデフォルトで数値データを持っている列ごとに1つの折れ線グラフを作成します。(plot()メソッドはSeriesとDataFrameともに使うことができます。)

特定の列のみプロットさせる場合は次のように書きます。(station_paris列)

1air_quality["station_paris"].plot()

デフォルトでは折れ線グラフですが、別のグラフにすることもできます。
例えばNO2の値をstation_london列とstation_paris列で比較したいときに散布図にする場合は次のように書きます。

1air_quality.plot.scatter(x="station_london", y="station_paris", alpha=0.5)

※プロットの種類についてはこちらを確認してください。

では、次のようにさらにプロットを拡張してみます。

1fig, axs = plt.subplots(figsize=(12, 4))
1air_quality.plot.area(ax=axs)
1axs.set_ylabel("NO$_2$ concentration")

 

既存の列から派生した新しい列を作成方法

「station_london」列のNO2濃度(mg/m3)の列を作りたい場合、次のように書きます。(NO2濃度は1.882をかけてやれば良いらしい)

1air_quality["london_mg_per_cubic"] = air_quality["station_london"] * 1.882

次に、「station_paris」列の値と「station_antwerp」列の比率を新しい列として次のようにして追加します。

1air_quality["ratio_paris_antwerp"] = (air_quality["station_paris"] / air_quality["station_antwerp"])

高度なロジックが必要な場合は、apply()を介して任意のPythonコードを使用することもできます。
例えば、列名を変更したい場合は次のように書きます。

1air_quality_renamed = air_quality.rename(
2  columns={
3    "station_antwerp": "BETR801",
4    "station_paris": "FR04014",
5    "station_london": "London Westminster",
6  }
7)

rename()関数は行名、列名ともに使うことができ、コロンの左側に現在の名前、右側に新しい名前を設定します。

 

次のようにして、列名を小文字に変換することもできます。

1air_quality_renamed = air_quality_renamed.rename(columns=str.lower)

 

要約統計量の計算方法

※ここからこちらのページの「Titanic data」を利用します。

統計の集計

まず、乗客の平均年齢を表示したいと思います。

mean()を使って次のように書きます。

1titanic["Age"].mean()

数値データを含む列に適用でき、デフォルトでは欠損データが除外され、行全体で計算されます。

続いて、乗客の年齢とチケット料金の価格のそれぞれの中央値を求めていたいと思います。
median()を使って次のように書きます。

1titanic[["Age", "Fare"]].median()

上記のように2つの列を指定するとそれぞれの列ごとで計算されます。

 

describe()でも確認してみます。

1titanic[["Age", "Fare"]].describe()

describe()のように事前に定義されているものの代わりに、agg()メソッドを使えば、特定の列に対して自分で設定した集計統計の組み合わせを定義することもできます。

1titanic.agg(
2  {
3    "Age": ["min", "max", "median", "skew"],
4    "Fare": ["min", "max", "median", "mean"],
5  }
6)

カテゴリごとにグループ化された統計の集計

乗客の男性と女性それぞれの平均年齢を出してみます。

groupby()メソッドを使って次のように書きます。

1titanic[["Sex", "Age"]].groupby("Sex").mean()

まず、「titanic[["Sex", "Age"]]」で性別と年齢の列を取得し、groupby()メソッドの引数に「"Sex"」を渡すことで性別ごとにグループを作成しています。

上記の例では、最初に2つの列(「Sex」と「Age」)を明示的に選択しました。選択しない場合に、meanメソッドに「numeric_only=True」を渡すことで、数値列を含む各列に平均法が適用されます。

1titanic.groupby("Sex").mean(numeric_only=True)

グループ化した後に列を選択して表示させることも可能です。

1titanic.groupby("Sex")["Age"].mean()

続いて、性別とキャビンクラスの組み合わせごとの航空券の平均運賃を求めてみたいと思います。
2つのカテゴリーの組み合わせを求めるには次のように書きます。

1titanic.groupby(["Sex", "Pclass"])["Fare"].mean()

 

 

カテゴリごとのレコード数をカウントする

各客室のクラスごとの乗客数を求めたい場合はvalue_counts()メソッドを使って次のように書きます。

1titanic["Pclass"].value_counts()

 

テーブルのレイアウトの変更の仕方

※ここからこちらのページの「Titanic data」と「Air quality data」を利用します。

次のようにして「Air quality data」を読み込んでおきます。

1 air_quality = pd.read_csv(
2    "data/air_quality_long.csv", index_col="date.utc", parse_dates=True
3)

テーブルの行の並べ替え

特定の列を指定して並べ替える場合、sort_valuses()が使えます。
「Titanic data」を乗客の年齢で並べ替える場合は次のように書きます。

1titanic.sort_values(by="Age").head()

また、客室クラスと年齢に応じて降順に並べ替えたい場合は次のように書きます。

1titanic.sort_values(by=['Pclass', 'Age'], ascending=False).head()

縦持ちから横持ちのテーブルへの変換

「parameter」がNO2のものを選択し、「location」でグループ化し、それぞれのグループの最初の2つの行を取得します。

1no2 = air_quality[air_quality["parameter"] == "no2"]
1no2_subset = no2.sort_index().groupby(["location"]).head(2)

 

上記で作った「no2_subset」DataFrameから3つの「location」の「value」を別の列として持つようなテーブルを表示したいと思います。
pivot()関数を使って次のように書きます。

1no2_subset.pivot(columns="location", values="value")

pivot()を使ってテーブルの形式を変えてから次のようにplotをすることも可能です。

1no2.pivot(columns="location", values="value").plot()

ピボットテーブル

各stationのNO2とPM2.5の平均値を求める場合はpivot_table()を使って次のように書きます。

1air_quality.pivot_table(
2  values="value", index="location", columns="parameter", aggfunc="mean"
3)

横持ちから縦持ちのテーブルへの変換

上記で作った「no2」という横持ちのテーブルにreset_index()を使って、新しいインデックスを追加してみます。

1no2_pivoted = no2.pivot(columns="location", values="value").reset_index()

横持ちのテーブルを縦持ちにするには、melt()メソッドを使って次のように書きます。

1no_2 = no2_pivoted.melt(id_vars="date.utc")

 

複数のテーブルの結合する方法

※下記ではこちらのページの「Air quality Nitrate data」と「Air quality Particulate matter data」を利用します。

次のようにして「Air quality Nitrate data」と「Air quality Particulate matter data」を読み込んでおきます。

1air_quality_no2 = pd.read_csv("data/air_quality_no2_long.csv", parse_dates=True)
1air_quality_no2 = air_quality_no2[["date.utc", "location", "parameter", "value"]]

1air_quality_pm25 = pd.read_csv("data/air_quality_pm25_long.csv", parse_dates=True)
1air_quality_pm25 = air_quality_pm25[["date.utc", "location", "parameter", "value"]]

オブジェクトの連結

「air_quality_no2」と「air_quality_pm25」を結合させるにはconcat()関数を使って次のように書きます。

1air_quality = pd.concat([air_quality_pm25, air_quality_no2], axis=0)

「axis」オプションを使って、結合する方向を設定します。デフォルト(axis=0)では、縦方向に連結します。

元のテーブルと連結後のテーブルの列数を確認すると下の画像のようにしっかりと結合されているのがわかります。

上記の結合したテーブルのdate.utcで並べ替えて、連結前のそれぞれの行が存在することを確認してみます。

1air_quality = air_quality.sort_values("date.utc")

今回の例では「parameter」列で連結前のテーブルのどちらのレコードかは識別できますが、常にそのようなテーブルを結合するとは限りません。
連結前のテーブルを識別させるためにconcat()関数ではkeys引数を使用して、インデックスを追加することができます。

1air_quality_ = pd.concat([air_quality_pm25, air_quality_no2], keys=["PM25", "NO2"])

共通の識別子を使用してテーブルを結合する

※下記でこちらのCSVを使います。

上記のCSVを読み込みます。

1stations_coord = pd.read_csv("data/air_quality_stations.csv")

前のセクションで作った「air_quality」と上記で読み込んだ「stasions_coord」には「location」という共通の列があり、次のようにmerge()関数を使用することでテーブルを結合することができます。

1air_quality = pd.merge(air_quality, stations_coord, how="left", on="location")

※下記でこちらのCSVを使います。
上記のCSVを読み込みます。

1air_quality_parameters = pd.read_csv("data/air_quality_parameters.csv")


前のセクションで作った「air_quality」と上記で読み込んだ「air_quality_parameters」には共通の列はありませんが、それぞれ「parameter」列と「id」列が共通のフォーマットの値を持っています。
上記の場合は、次のようにmerge()関数のleft_onright_onを使用することでテーブルを結合することができます。

1air_quality = pd.merge(air_quality, air_quality_parameters, how="left", left_on="parameter", right_on="id")

 

時系列データを簡単に扱う方法

※下記ではこちらのページの「Air quality data」を利用します。

次のようにして「Air quality data」を読み込んでおきます。

air_quality = pd.read_csv("data/air_quality_no2_long.csv")
air_quality = air_quality.rename(columns={"date.utc": "datetime"})

 

pandasの日時プロパティの使用

「datetime」列を日付のテキストではなく、datetime(pandas.Timestamp)として扱いたい場合、次のように書きます。

1air_quality["datetime"] = pd.to_datetime(air_quality["datetime"])

pandas.Timestampオブジェクトにすると下記のようなユースケースで役に立ちます。

 

  • 時系列のデータセットの開始日と終了日を求めたいとき

日付情報を計算して比較できるようになるので、次のように書くと求められます。

1air_quality["datetime"].min(), air_quality["datetime"].max()
  • 測定月のみを含む新しい列をDataFrameに追加したいとき

時間関連のプロパティを使えるようになるので、例えばmonthを使って次のようにDataFrameに「month」列を追加することができます。

1air_quality["month"] = air_quality["datetime"].dt.month

  • 各測定場所の各曜日の平均NO2濃度を求めたいとき
1air_quality.groupby([air_quality["datetime"].dt.weekday, "location"])["value"].mean()

 

インデックスとしての日時

前のセクションでテーブルを再形成するために次のようにpivot() を使っていました。

1no_2 = air_quality.pivot(index="datetime", columns="location", values="value")


上記のようにpivotすることで日時情報がテーブルのインデックスとなっています。

日時インデックスには強力な機能があります。たとえば、時系列プロパティを取得するためにdtは必要ありません。次のように、これらのプロパティはインデックスで直接利用できます。

1no_2.index.year, no_2.index.weekday

では次のようにしt、2019年5月20~21でのさまざまな観測所からのNO2の値のプロットを作成してみます。

1no_2["2019-05-20":"2019-05-21"].plot();

 

時系列を別の頻度に再サンプルする

例えば、現在の1時間ごとの時系列の値を、各観測所の月間最大値に集約したい場合はresample()メソッドを使って次のように書きます。

1monthly_max = no_2.resample("M").max()

また、時系列の頻度はfreqで次のように取得できます。

1monthly_max.index.freq

各ステーションのNO2値の日次平均値のプロットを作成するには次のように書きます。

1no_2.resample("D").mean().plot(style="-o", figsize=(10, 5));

 

テキストデータを扱う方法

※下記ではこちらのページの「Titanic data」を利用します。

次のようにして「Titanic data」を読み込んでおきます。

1titanic = pd.read_csv("data/titanic.csv")

「Name」列の文字を全て小文字にするには、lower()メソッドを使って次のように書きます。

1titanic["Name"].str.lower()

上記のように「str」アクセサを使ってデータの各要素に対して文字列のメソッドを適用することができます。

「Name」列のカンマの前の部分を抽出して、「Surname」という新しいカラムを作りたい場合、str()メソッドを使って次のように書きます。

1titanic["Name"].str.split(",")


上記のように2つの要素(「Name」列のカンマの前と後ろの文字)のリストとして返されます。

カンマの前の部分を抽出するには、get()メソッドを使って次のように書きます。

1titanic["Surname"] = titanic["Name"].str.split(",").str.get(0)

乗客の中の伯爵夫人(「Name」列に「Countess」が含まれる)に関する乗客データを抽出したい場合、contains()メソッドを使って次のように書きます。

1titanic[titanic["Name"].str.contains("Countess")]

titanic["Name"].str.contains("Countess")の部分で「Name」列に「Countess」という単語が膨れまているかどうかをチェックして、各値にTrueかFalseを返しています。

乗客の中で最も長い名前の人を抽出したい場合、まずはlen()メソッドを使って次のように書いて長さを取得します。

1titanic["Name"].str.len()

続いて、テーブル内で最も長い名前の人の位置のインデックスを取得するために、idxmax()メソッドを使って次のように書きます。

1titanic["Name"].str.len().idxmax()

最後に上記で求めてインデックスを使用して、次のようにして名前を取得します。

1titanic.loc[titanic["Name"].str.len().idxmax(), "Name"]

「Sex」列で、「male」の値を「M」に置き換え、「female」の値を「F」に置き換えたい場合、replace()メソッドを使って次のように書きます。

1titanic["Sex_short"] = titanic["Sex"].replace({"male": "M", "female": "F"})

 

 

おわりに

想像以上にボリュームがあり大変でしたが、勉強になりました!

データ比較を視覚的にわかりやすく見せるためにグラフを作ることが多いので、特にplotの箇所はドキュメントを読んでさらに深掘りしつつたくさんグラフを作っていきたいと思います。(使わな忘れる!)

 

参照URL

https://pandas.pydata.org/docs/getting_started/intro_tutorials/index.html

 

ライトコードでは、エンジニアを積極採用中!

ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

採用情報へ

モリ(エンジニア)
モリ(エンジニア)
Show more...

おすすめ記事

エンジニア大募集中!

ライトコードでは、エンジニアを積極採用中です。

特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

また、フリーランスエンジニア様も大募集中です。

background