Pythonで統計・データ分析!〜基本統計量の活用と機械学習の基本数学×Pythonプログラミング入門(1/5 ページ)

データ分析において最もよく使われる表形式のデータを取り扱う方法を見ていく。まず、pandasデータフレームの基本的な取り扱い方法を確認し、次に、各種の基本統計量を求める。また、基本統計量の可視化を行い、データの「見方」についても触れる。最後に、scikit-learnを使った回帰と分類の簡単な例を紹介する。

» 2023年01月23日 05時00分 公開
[羽山博]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「数学×Pythonプログラミング入門」のインデックス

連載目次

 前回は三角関数の基本とサウンドデータの取り扱いを紹介しました。今回はデータ分析の基本に立ち返り、まず、pandasデータフレームを利用して、表形式のデータを取り扱う方法を見ていきます。続いて、それらのデータを基に平均や標準偏差、四分位数、相関係数などを簡単に求め、可視化によりデータの特徴を見える化します。ここでは、標準偏差や相関係数などの統計量の計算方法そのものよりも、データの取り扱いやそれらの統計量をどう見るかといった部分に焦点を当てることにします。また、機械学習の簡単な例についてもデータの取り扱い方を中心に見ていきます。

連載:

『数学×Pythonプログラミング入門 ― 中学・高校数学で学ぶ』

数学×Pythonプログラミング入門

この連載では、中学や高校で学んだ数学を題材にして、Pythonによるプログラミングを学びます。といっても、数学の教科書に載っている定理や公式だけに限らず、興味深い数式の例やAI/機械学習の基本となる例を取り上げながら、数学的な考え方を背景としてプログラミングを学ぶお話にしていこうと思います。

羽山博 羽山博

筆者紹介: IT系ライター、大学教員(非常勤)。書道、絵画を経て、ピアノとバイオリンを独学で始めるも学習曲線は常に平坦。趣味の献血は、最近脈拍が多く99回で一旦中断。さらにリターンライダーを目指し、大型二輪免許を取得するもバイクの購入資金が全くない。


 分析の対象となるデータの多くは、表の形式になっています。それらは、CSVファイルやExcelのブックなどに記録されていることが多いので、そういったデータを読み込むことがデータ分析の出発点になります。ここでは、Google Colabで提供されているカリフォルニアの住宅価格データ('sample_data/california_housing_train.csv')をpandasデータフレームに読み込み、行や列を取り出してみることを最初の目標にします。

 Google Colab以外の環境であれば、こちらから同じデータがダウンロードできるので、それを利用するといいでしょう。

 pandasデータフレームやデータフレームの行や列の取り扱い方法を確認するために、さらに、以下の3つの処理にも取り組みましょう。

  • 列を取り出す: 各区画の人口(population)を取り出し、その平均を求める
  • 1つの列から条件を満たす行を取り出す: 各区画の築年数の中央値(housing_median_age)が20年未満のデータの一覧を作る。また、全体に対する割合を求める
  • 複数の列から、複数の条件を満たす行を取り出す: 各区画の人口が300未満で、かつ、住宅価格の中央値(median_house_value)が50000ドル未満のデータの一覧を表示する

 すでにpandasデータフレームに精通している方は目標2へ進んでいただいてけっこうです(とはいえ、もしかすると新しい発見があるかもしれないので、ざっと目を通していただくのもいいかと思います)。ここでは、データの基本的な取り扱いのために使う最小限の機能を見ていくので、詳細については、pandasのユーザーガイドAPIリファレンスを参照してください。

1. pandasデータフレームの行や列を取り扱うためのコード

 まずは、CSVファイルのデータをデータフレームに読み込みます。基本はpandasモジュールのread_csv関数にファイル名やURLを指定するだけです。ここでは、読み込んだデータフレームをdfという変数名で参照できるようにしましょう。先頭から何行かを表示するには、データフレームのheadメソッドを使います。

import pandas as pd

df = pd.read_csv('./sample_data/california_housing_train.csv')
df.head()

リスト1 CSVファイルからデータフレームにデータを読み込むコード
headメソッドは先頭から何件かのデータを返す。件数は引数に指定する。この例のように、件数を省略した場合は先頭から5件のデータが返される。

 コードを実行すると、次の図1に示されるような表形式のデータが表示されます。図1には、データフレームを取り扱うための基本概念も併せて示しています。

データフレーム 図1 リスト1の実行例とデータフレームの仕組み
データフレームの行や列を特定するための見出しは一般にインデックスと呼ばれるが、行見出し(行ラベル)はindex属性で参照され、列見出し(列ラベル)はcolumns属性で参照される。データフレームに含まれる単一の行や単一の列はシリーズSeries)と呼ばれる。データフレームに含まれる何行何列かのデータをデータフレームとして取り扱うこともできる。

 図1に示したデータフレームの仕組みについては動画でも解説しておきます。pandasにあまり慣れていない方はぜひ視聴してみてください。

動画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')  # 列のインデックス(列見出し)

リスト2 行と列のインデックスを一覧表示する
自動的に付番された行のインデックス(行見出し)はRangeIndexというオブジェクトになっている。CSVファイルの1行目の項目名が列のインデックス(列見出し)になっていることも分かる。

 各項目(列見出し)は、緯度(longitude)、経度(latitude)、築年数の中央値(housing_median_age)、部屋数の合計(total_rooms)、寝室数の合計(total_bedrooms)、人口(population)、世帯数(households)、世帯収入の中央値(median_income)、住宅価格の中央値(median_house_value)となっています。

 これらの住宅価格のデータは、個々の物件の値ではなく、1区画(ブロック)の合計や中央値となっています。以降、「部屋数の合計」などは単に「部屋数」と表記し、「築年数の中央値」などは単に「築年数」と表記することにします。

コラム さまざまな形式のCSVファイルを読み込む

 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()

リスト3 タブ区切りのCSVファイルをそのまま読み込むコード
記録されているデータの形式に合わせず、そのままCSVファイルを読み込もうとした例。実行例は図2。

CSVファイルの読み込み 図2 リスト3の実行結果(正しく読み込まれなかった例)
区切り文字を指定しないと、カンマが区切り文字であると見なされる。元のデータでは、売上金額の桁区切りの記号としてカンマが使われているので、その位置で列が区切られてしまう。従って5行3列のデータフレームになった。

import pandas as pd
dfsales = pd.read_csv('https://raw.githubusercontent.com/Gessys/math/main/data/sales.csv', sep='\t', index_col='担当者コード')
dfsales.head()

リスト4 区切り文字と行のインデックスとする列を指定してCSVファイルを読み込むコード
記録されているデータの形式と合わせてCSVファイルを読み込む。区切り文字は引数sepに指定する。また、行のインデックスを指定したい場合は、引数index_colに列位置か列見出しを指定する。担当者コードは0列目なのでindex_col=0と指定してもよい。実行例は図3。

CSVファイルの読み込み 図3 リスト4の実行結果(区切り文字とインデックスを指定してCSVファイルを読み込んだ例)
区切り文字を正しく指定したので、正しく列が区切られた。担当者コードの列をインデックスに指定したので、担当者コードが行のインデックス(行見出し)になる。インデックスはデータとは別の扱いになるので、氏名の列が0列目になることに注意。


コラム Excelのファイルを読み込む

 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()

リスト5 Excelのデータを読み込むコード
read_excel関数のsheet_name引数に指定したワークシートからデータが読み込める。不要な行はskiprows引数に先頭からの行数や行のリストを指定することによって除外できる。例えば、上の例ならskiprows=[0, 1]としてもよい。読み込む列はusecols引数に先頭からの位置をリストで指定する。usecols引数には列見出しのリストも指定できる。上の例ならusecols=['移動距離(px)', '移動時間(ms)']でもよい(図4の実行例を参照)。指定できる引数の詳細についてはこちらを参照のこと。

Excelのデータ
読み込まれたExcelのデータ" 図4 リスト5の実行結果(Excelのデータを読み込む)
上の画面が元のデータ。下の画面が読み込まれたデータフレームの先頭から5行。


データフレームから単一の列を取り出す

 次に、リスト1で読み込んだ住宅価格データ(データフレーム名:df)から人口(population)の列を取り出しましょう。特定の列のデータを全て取り出す最も簡単な方法は、データフレーム名.列見出しまたはデータフレーム名['列見出し']という形式で列を指定する方法です。

目的 書き方
単一の列(Series)を取り出す データフレーム名.列見出しまたはデータフレーム名['列見出し']
表1 単一の列を取り出す
[]の中に列見出しを指定すれば、その列を取り出せる。取り出したデータは1列のデータなので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 人口の一覧を表示し、平均値を求める
それぞれ、print(df.population)print(df.population.mean())としても同じ結果が得られる。

 リスト6のようにdf['population']とすると、取り出されたデータはSeriesとなりますが、df[['population']]とすると、取り出されたデータはデータフレームになります。この違いについては次に説明します。

データフレームから複数の列を取り出す

 データフレームから複数の列を取り出すには、データフレーム名[列見出しのリスト]という形式で複数の列を指定します。列見出しのリストは、['列見出し', '列見出し', ...]という形式になるので、表2のように[]が二重になります。

目的 書き方
複数の列(DataFrame)を取り出す データフレーム名[['列見出し', '列見出し', ... ]]
表2 複数の列を取り出す
[]の中に列見出しのリストを指定すれば、複数の列を取り出せる。取り出したデータは複数列のデータなのでデータフレームとなる。

 例えば、人口(population)と住宅価格(median_house_value)を取り出す場合は、リスト7と図5のようになります。

# リスト6の続きに入力する
df[['population', 'median_house_value']]

リスト7 人数と住宅価格の一覧を表示するコード
データフレーム名[列見出しのリスト]の形式で取り出したい複数の列を指定する。[]の中にリストを指定するので、[]が二重になることに注意。

複数の列を表示する 図5 リスト7の実行結果(人数と住宅価格の一覧を表示する)
複数の列が取り出せた。取り出したデータはデータフレームとなる。

 上でも述べた通り、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['行見出し', '列見出し']
表3 行と列を取り出す
ilocメソッドでは取り出す行や列を整数で指定する(先頭を0とする)。locメソッドでは取り出す行や列をインデックス(見出し)で指定する。行や列の指定にはスライス機能が使える。ilocメソッドでは末尾が含まれず、locメソッドでは末尾が含まれることに注意。

 具体例を見てみましょう。あまり意味のない例ですが、2行目〜4行目までで、6列目(households)以降を取り出してみます。リスト8はilocメソッドを、リスト9はlocメソッド使うコード例です。両方とも同じ図6の結果になります。

# リスト7の続きに入力する
df.iloc[2:5, 6:]

リスト8 行位置と列位置を整数で指定してデータを取り出すコード
ilocメソッドでは、取り出すデータを行位置と列位置を整数で指定する。2:5は2行目〜5行目の手前(つまり4行目まで)、6:は6列目以降を表す。実行結果は図6の通り。

# リスト8の続きに入力する
df.loc[2:4, 'households':]

リスト9 インデックス(行見出しと列見出し)を指定してデータを取り出すコード
locメソッドでは、取り出すデータを行見出しと列見出しで指定する。2:42というインデックスから4というインデックスまで、という意味になる。ilocメソッドとは異なり、末尾も含まれることに注意が必要。'households':は'households'以降の全ての列という意味になる。最後の列見出しが'median_house_value'なので、'households':'median_house_value'としてもよい。実行結果は図6の通り。

複数の列を表示する 図6 リスト8とリスト9の実行結果(行と列を指定してデータを取り出す)
複数行、複数列が取り出された。取り出されたデータはデータフレームとなる。

 いずれの場合も、全ての行や列を表すには:だけを指定します。例えば、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

リスト10 列のデータが条件に当てはまるかどうかを表示する
演算子<の左辺は、列のデータ(Series)を取り出すコードなので、df['housing_median_age'] < 20と書いてもdf.loc[:, 'housing_median_age'] < 20と書いても同じことができる。

 さて、ここからが面白いところです。データフレームに真偽値のリスト*1を与えると、Trueに一致する行のデータだけが取り出されます。従って、df[]の中にリスト10のコードを書けば、築年数が20年未満のデータだけが取り出せます。この仕組みについては動画でも解説しておきます。ぜひご覧ください。


注1

*1 リストだけでなく、NumPyの配列やpandasのSeriesでも構いません。リスト10の例ではpandasのSeriesが求められています。


動画2 pandasのフィルタリングの仕組み


 コードは以下の通りです。df[]の中にリスト10のコードをそのまま書けばいい、ということでしたね。

# リスト10の続きに入力する
df[df.housing_median_age < 20]

リスト11 条件に当てはまる行を表示するコード
真偽値により取り出す行を指定する。リスト10で見たdf.housing_median_age < 20の結果に従って、Trueになっている行だけが返される。実行例は図7の通り。

条件に当てはまる行を表示する 図7 リスト11の実行結果(築年数が20年未満のデータを表示する)
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

リスト12 条件に当てはまる行の割合を求める
True1False0として扱うことができるので、sumメソッドを使って合計を求めると、Trueの個数が求められる。countメソッドで求めた全体の個数で割れば、全体に対する割合が求められる。

複数の条件に一致する行や列を取り出す

 条件が複数になっても、上で見た方法の応用で目的の行や列が取り出せそうです。ここでは、あまり人のいない場所で、住宅価格が安いのはどこかを知るために、人口が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 複数の条件に当てはまる行を表示するコード
&演算子などのビットごとの論理演算子は<演算子などの比較演算子よりも優先順位が高いので、&演算子や|演算子を使う場合は、それぞれの条件を()で囲んでおく必要がある。実行例は図8。

# リスト13の続きに入力する
df.query('population < 100 and median_house_value < 30000')

リスト14 複数の条件に当てはまる行を表示するコード(queryメソッドを使う)
queryメソッドを使えば、文字列で指定した条件に従って行が取り出せる。実行例は図8の通り。

条件に当てはまる行を表示する 図8 リスト13とリスト14の実行結果(複数の条件に当てはまる行を表示する)
人口が100未満で、かつ、住宅価格が30000ドル未満のデータの一覧が表示された。

 特定の列を取り出したければ、locメソッドを使って、

df.loc[(df.population < 100) & (df.median_house_value < 30000), 'median_house_value']


のようにすればいいでしょう。

       1|2|3|4|5 次のページへ

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。