データ分析において最もよく使われる表形式のデータを取り扱う方法を見ていく。まず、pandasデータフレームの基本的な取り扱い方法を確認し、次に、各種の基本統計量を求める。また、基本統計量の可視化を行い、データの「見方」についても触れる。最後に、scikit-learnを使った回帰と分類の簡単な例を紹介する。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
前回は三角関数の基本とサウンドデータの取り扱いを紹介しました。今回はデータ分析の基本に立ち返り、まず、pandasデータフレームを利用して、表形式のデータを取り扱う方法を見ていきます。続いて、それらのデータを基に平均や標準偏差、四分位数、相関係数などを簡単に求め、可視化によりデータの特徴を見える化します。ここでは、標準偏差や相関係数などの統計量の計算方法そのものよりも、データの取り扱いやそれらの統計量をどう見るかといった部分に焦点を当てることにします。また、機械学習の簡単な例についてもデータの取り扱い方を中心に見ていきます。
『数学×Pythonプログラミング入門 ― 中学・高校数学で学ぶ』
この連載では、中学や高校で学んだ数学を題材にして、Pythonによるプログラミングを学びます。といっても、数学の教科書に載っている定理や公式だけに限らず、興味深い数式の例やAI/機械学習の基本となる例を取り上げながら、数学的な考え方を背景としてプログラミングを学ぶお話にしていこうと思います。
筆者紹介: IT系ライター、大学教員(非常勤)。書道、絵画を経て、ピアノとバイオリンを独学で始めるも学習曲線は常に平坦。趣味の献血は、最近脈拍が多く99回で一旦中断。さらにリターンライダーを目指し、大型二輪免許を取得するもバイクの購入資金が全くない。
分析の対象となるデータの多くは、表の形式になっています。それらは、CSVファイルやExcelのブックなどに記録されていることが多いので、そういったデータを読み込むことがデータ分析の出発点になります。ここでは、Google Colabで提供されているカリフォルニアの住宅価格データ('sample_data/california_housing_train.csv')をpandasデータフレームに読み込み、行や列を取り出してみることを最初の目標にします。
Google Colab以外の環境であれば、こちらから同じデータがダウンロードできるので、それを利用するといいでしょう。
pandasデータフレームやデータフレームの行や列の取り扱い方法を確認するために、さらに、以下の3つの処理にも取り組みましょう。
すでにpandasデータフレームに精通している方は目標2へ進んでいただいてけっこうです(とはいえ、もしかすると新しい発見があるかもしれないので、ざっと目を通していただくのもいいかと思います)。ここでは、データの基本的な取り扱いのために使う最小限の機能を見ていくので、詳細については、pandasのユーザーガイドやAPIリファレンスを参照してください。
まずは、CSVファイルのデータをデータフレームに読み込みます。基本はpandasモジュールのread_csv関数にファイル名やURLを指定するだけです。ここでは、読み込んだデータフレームをdfという変数名で参照できるようにしましょう。先頭から何行かを表示するには、データフレームのheadメソッドを使います。
import pandas as pd
df = pd.read_csv('./sample_data/california_housing_train.csv')
df.head()
コードを実行すると、次の図1に示されるような表形式のデータが表示されます。図1には、データフレームを取り扱うための基本概念も併せて示しています。
図1に示したデータフレームの仕組みについては動画でも解説しておきます。pandasにあまり慣れていない方はぜひ視聴してみてください。
なお、これ以降、データフレームを参照する変数を単に「データフレーム」と呼ぶことにします。例えば、リスト1の例であれば、データフレームを参照する変数がdfという名前なので「dfというデータフレーム」といった表記にします。
# リスト1の続きに入力する
print(df.index)
print(df.columns)
# 出力例:
# RangeIndex(start=0, stop=17000, step=1) # 行のインデックス(0〜16999)
# Index(['longitude', 'latitude', 'housing_median_age', 'total_rooms',
# 'total_bedrooms', 'population', 'households', 'median_income',
# 'median_house_value'],
# dtype='object') # 列のインデックス(列見出し)
各項目(列見出し)は、緯度(longitude)、経度(latitude)、築年数の中央値(housing_median_age)、部屋数の合計(total_rooms)、寝室数の合計(total_bedrooms)、人口(population)、世帯数(households)、世帯収入の中央値(median_income)、住宅価格の中央値(median_house_value)となっています。
これらの住宅価格のデータは、個々の物件の値ではなく、1区画(ブロック)の合計や中央値となっています。以降、「部屋数の合計」などは単に「部屋数」と表記し、「築年数の中央値」などは単に「築年数」と表記することにします。
read_csv関数では、項目の区切りがカンマでない場合には、引数にsep=区切り文字を指定します。リスト1のように、特に何も指定せずにCSVファイルのデータを読み込んだ場合、先頭の行が列のインデックス(列見出し)として扱われ、行のインデックスは自動的に付番されます。ただし、行のインデックスとして取り扱いたい列を指定したいときにはindex_col=列の形式で指定します。「列」には0から始まる整数か、列見出しが指定できます。
タブ文字(\t)で区切られたCSVファイルを、そのまま読み込んだ場合(リスト3と図2)と、区切り文字を指定して読み込んだ場合の例(リスト4と図3)を以下に示しておきます。後者では、行のインデックスとして「担当者コード」を指定しています。データはWeb上に置いてあるものを使うことにします。
import pandas as pd
dfsales = pd.read_csv('https://raw.githubusercontent.com/Gessys/math/main/data/sales.csv')
dfsales.head()
import pandas as pd
dfsales = pd.read_csv('https://raw.githubusercontent.com/Gessys/math/main/data/sales.csv', sep='\t', index_col='担当者コード')
dfsales.head()
Excelのデータはpandasモジュールのread_excel関数でデータフレームに読み込めます。Excelのブックには複数のワークシートがあるので、どのワークシートからデータを読み込むかを指定する必要があります(指定しないと、先頭のワークシートからデータが読み込まれます)。
import pandas as pd
dffitts = pd.read_excel('https://github.com/Gessys/math/blob/main/data/fitts.xlsx?raw=true', sheet_name='実験データ', skiprows=2, usecols=[0, 1])
dffitts.head()
次に、リスト1で読み込んだ住宅価格データ(データフレーム名:df)から人口(population)の列を取り出しましょう。特定の列のデータを全て取り出す最も簡単な方法は、データフレーム名.列見出しまたはデータフレーム名['列見出し']という形式で列を指定する方法です。
目的 | 書き方 |
---|---|
単一の列(Series)を取り出す | データフレーム名.列見出しまたはデータフレーム名['列見出し'] |
以下のリスト6では、人口の列を取り出すとともに、その平均値も求めてみます。データフレームやSeriesの平均値はmeanメソッドを使えば簡単に求められます。
import pandas as pd
df = pd.read_csv('./sample_data/california_housing_train.csv')
print(df['population'])
print(df['population'].mean())
# 出力例:
# 0 1015.0
# 1 1129.0
# 2 333.0
# 3 515.0
# 4 624.0
# ...
# 16995 907.0
# 16996 1194.0
# 16997 1244.0
# 16998 1298.0
# 16999 806.0
# Name: population, Length: 17000, dtype: float64
# 1429.5739411764705 # 人口の平均値
リスト6のようにdf['population']とすると、取り出されたデータはSeriesとなりますが、df[['population']]とすると、取り出されたデータはデータフレームになります。この違いについては次に説明します。
データフレームから複数の列を取り出すには、データフレーム名[列見出しのリスト]という形式で複数の列を指定します。列見出しのリストは、['列見出し', '列見出し', ...]という形式になるので、表2のように[]が二重になります。
目的 | 書き方 |
---|---|
複数の列(DataFrame)を取り出す | データフレーム名[['列見出し', '列見出し', ... ]] |
例えば、人口(population)と住宅価格(median_house_value)を取り出す場合は、リスト7と図5のようになります。
# リスト6の続きに入力する
df[['population', 'median_house_value']]
上でも述べた通り、df['population']では、取り出されたデータはSeriesですが、df[['population']]とすると、取り出されたデータはデータフレームとなります。後者は表2やリスト7の例で、列見出しのリストに項目が1つだけしか指定されていなかった場合に当たるものと考えれば納得できるでしょう。
ところで、ここで見た方法ではリストのスライスのような「:」による範囲指定はできません。例えば、人口(population)から住宅価格(median_house_value)までを取り出したいときにdf[['population': 'median_house_value']]としてもエラーになります。行や列の範囲を指定してデータを取り出す方法については、次の項で説明します。
これまで見てきた方法では、特定の列は取り出せますが、特定の行を取り出すことはできません。特定の行や列を自由に指定して取り出すには、ilocメソッドやlocメソッドが使えます。ilocメソッドは取り出す行や列の位置を整数で指定します。一方のlocメソッドは取り出す行や列のインデックス(見出し)を指定します。どちらのメソッドでも、スライスの指定ができます。ただし、ilocメソッドの場合は末尾が含まれず、locメソッドの場合は末尾が含まれることに注意が必要です。
目的 | 書き方 |
---|---|
整数で位置を指定して行と列を取り出す | データフレーム名.iloc[行位置, 列位置] |
インデックスを指定して行と列を取り出す | データフレーム名.loc['行見出し', '列見出し'] |
具体例を見てみましょう。あまり意味のない例ですが、2行目〜4行目までで、6列目(households)以降を取り出してみます。リスト8はilocメソッドを、リスト9はlocメソッド使うコード例です。両方とも同じ図6の結果になります。
# リスト7の続きに入力する
df.iloc[2:5, 6:]
# リスト8の続きに入力する
df.loc[2:4, 'households':]
いずれの場合も、全ての行や列を表すには:だけを指定します。例えば、df.iloc[:, 3:5]とすると、全ての行の3列目から5列目の手前(4行目まで)という意味になります。
次の目標は、築年数(housing_median_age)が20年未満のデータの一覧を作るというものです。つまり、条件に一致する行や列を取り出すというわけです。また、全体に対する割合も求めましょう。
いきなり条件を付けて検索を行うのは難しいので、前提として、各行の築年数が20年未満かどうかを表示してみましょう(20年未満ならTrue、そうでないならFalseと表示します)。データフレームでもNumPyのブロードキャスト機能と同様、複数の値に対して同じ演算を一度に行うことができるので、リスト10のようにすれば簡単です。
# リスト9の続きに入力する
df.housing_median_age < 20
# 出力例:
# 0 True
# 1 True
# 2 True
# 3 True
# 4 False
# ...
# 16995 False
# 16996 False
# 16997 True
# 16998 True
# 16999 False
# Name: housing_median_age, Length: 17000, dtype: bool
さて、ここからが面白いところです。データフレームに真偽値のリスト*1を与えると、Trueに一致する行のデータだけが取り出されます。従って、df[]の中にリスト10のコードを書けば、築年数が20年未満のデータだけが取り出せます。この仕組みについては動画でも解説しておきます。ぜひご覧ください。
*1 リストだけでなく、NumPyの配列やpandasのSeriesでも構いません。リスト10の例ではpandasのSeriesが求められています。
コードは以下の通りです。df[]の中にリスト10のコードをそのまま書けばいい、ということでしたね。
# リスト10の続きに入力する
df[df.housing_median_age < 20]
df.locメソッドを使えば、条件に当てはまる行のうち、特定の列を取り出すこともできます。例えば、築年数が20年未満の住宅価格(median_house_value)だけを取り出すなら、
df.loc[df.housing_median_age < 20, 'median_house_value']
とすればいいでしょう。ただし、ilocメソッドではこのような真偽値による指定はできません。
続いて、築年数が20年未満の行の割合を求めましょう。わざわざ行や列を取り出して数えなくても、リスト10で求められた列に含まれるTrueの個数を数えるといいですね。
# リスト11の続きに入力する
hma = df.housing_median_age < 20 # リスト10で求めた値(TrueかFalseのSeries)
print(hma.sum() / hma.count())
# 出力例:
# 0.2838823529411765
条件が複数になっても、上で見た方法の応用で目的の行や列が取り出せそうです。ここでは、あまり人のいない場所で、住宅価格が安いのはどこかを知るために、人口が100未満で、かつ、住宅価格が30000ドル未満のデータの一覧を表示してみましょう。
「人口が100未満で、かつ、住宅価格が30000ドル未満」かどうかは、df.population < 100 and df.median_house_value < 30000と表せるような気がします。しかし、and演算子はブロードキャスト機能に対応していないのでうまくいきません。解決方法としては、and条件を表すのに&演算子を使う方法(or条件であれば、代わりに|演算子を使えばよい)と、queryメソッドに条件を文字列として指定する方法の2つがあります。リスト13が前者の方法で、リスト14が後者の方法のサンプルコードです。いずれの実行結果も図8のように同じです。
# リスト12の続きに入力する
df[(df.population < 100) & (df.median_house_value < 30000)]
# リスト13の続きに入力する
df.query('population < 100 and median_house_value < 30000')
特定の列を取り出したければ、locメソッドを使って、
df.loc[(df.population < 100) & (df.median_house_value < 30000), 'median_house_value']
のようにすればいいでしょう。
Copyright© Digital Advantage Corp. All Rights Reserved.