Pandas:データ分析#

in English or the language of your choice.

import pandas as pd
import py4macro

# 警告メッセージを非表示
import warnings
warnings.filterwarnings("ignore")

説明#

Pandasはデータを扱う上で欠かせないパッケージであり,エクセルをイメージすれば良いだろう。Pandasにはエクセルのスプレッド・シートに対応するDataFrame(データ・フレーム)とSeries(シリーズ)呼ばれるオブジェクトがあり,それらを駆使してデータ分析をおこなう。

DataFrameは次の表の様になっている。まず、用語の整理をしよう。

X

Y

Z

0

10

5.0

3.0

1

20

30.0

2.0

2

30

15.0

5.0

  • とは縦に表示されたデータの集まりであり、Xの列には102030がある。YZも同様にそれぞれの値から構成される列となる。

  • とは横に表示されたデータの集まりであり、0の行には105.03.0がある。12も同様にそれぞれの値から構成される行となる。

行と列には、その位置を示す番号とラベルがある。このサイトでは次の呼称とする。

  • 位置を示す番号をインデックスもしくはインデックス番号と呼ぶ。

    • 上の例では、左端の00番目の行インデックス、11番目の行インデックス、22番目の行インデックスを示す。

    • 上の例では、一番上のXには0の列インデックスが付与されており、Yには1の列インデックスが付与されており、Zには2の列インデックスが付与されている。

  • 位置をラベルで表すこともできる。

    • 上の例では、行ラベルは設定されていない。

    • 上の例では、一番上のXYZが列ラベルとなる。

上の例のように、DataFrameは1つ以上の行と列から構成されている。一方、Seriesには1つの列(もしくは行と考えることもできる)のみしかない点が大きな違いである。

これらの特徴により複雑なデータの扱いが簡単になる。例えば,NumPyを使うと目的のデータにアクセスしたい場合、行番号と列番号を把握する必要がある。一方、Pandasでは行と列にラベルを使うことにより(例えば,行Japanの列gdpのようにデータを特定することが可能となる),データの操作が非常に簡単になる。また,PandasNumPyに基づいているため,ベクトル演算の機能が使える。

ここで説明できない他の使い方についてはこのサイトこのサイトが参考になるだろう。

Pandasは通常pdという名前で読み込む。

import pandas as pd

データの読み込みとデータのチェック#

様々なデータを読み込むことが可能だが,ここではread_csv()関数を使って.csvファイルを読み込むことにする。(csvはcomma separated valuesの略称であり、.csvファイルについてはこの検索結果を参照)ここで使うdata1.csvここをクリックをマウスの右クリックすることによりダウンロードすることもできる。

# データの読み込み
df = pd.read_csv('./data/data1.csv')

df全体を表示させる。

df
country gdp con inv pop continent
0 China 20118 7796.0 9137.0 1434 Asia
1 France 2950 1598.0 806.0 67 Europe
2 Germany 4274 2294.0 971.0 84 Europe
3 India 9142 5936.0 2471.0 1366 Asia
4 Italy 2463 1423.0 614.0 61 Europe
5 Japan 5032 2675.0 1238.0 127 Asia
6 Korea 2157 942.0 705.0 51 Asia
7 Singapore 477 176.0 135.0 6 Asia
8 Taiwan 1100 607.0 NaN 24 Asia
9 UK 2994 NaN NaN 68 Europe

数値はPenn World Tableと呼ばれるデータセットに基づき作成している。

  • country:国名

  • gdp:国内総生産(Gross Domestic Product, current PPPs; in billions 2011US$)

  • con:消費(consumption, current PPPs; in billions 2011US$)

  • inv:投資(investment, current PPPs; in billions 2011US$)

  • pop:人口(population, in millions)

  • continent:大陸(continent)

行はインデックス(番号)になっており,そのまま使っても全く問題ない。ここでは列countryを行ラベルに設定してPandasの使い方について説明することにする。

  • set_index():引数の列を行ラベルにするメソッド(行ラベルは文字列とすること)

df = df.set_index('country')
df
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
France 2950 1598.0 806.0 67 Europe
Germany 4274 2294.0 971.0 84 Europe
India 9142 5936.0 2471.0 1366 Asia
Italy 2463 1423.0 614.0 61 Europe
Japan 5032 2675.0 1238.0 127 Asia
Korea 2157 942.0 705.0 51 Asia
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia
UK 2994 NaN NaN 68 Europe

Tip

  • df.set_index('country')は直接dfに影響を与えない。単に,書き換えるとどうなるかを表示しているだけとなる。ここではdfに再度割り当てることによりdf自体を上書きしている。

  • 出力にあるNaN(Not a Number)は欠損値を示す。

  • 行ラベルにcountryという列ラベルが残るが,それを消すにはメソッド.rename_axis('')を使うと良いだろう。ここで''は空の文字列である。.rename_axis(None)でも同じ結果となる。もしくは次のコードでも良い。

df.index.name = '' 

行数が多い場合(例えば,10000)、全てを表示してもあまり意味がない。そこでよく使うdfのメソッドに、最初や最後の数行だけを表示するメソッド.head().tail()を紹介する。

dfの最初の5行を表示させる。

df.head()
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
France 2950 1598.0 806.0 67 Europe
Germany 4274 2294.0 971.0 84 Europe
India 9142 5936.0 2471.0 1366 Asia
Italy 2463 1423.0 614.0 61 Europe

引数に2を指定すると最初の2行のみ表示される。

df.head(2)
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
France 2950 1598.0 806.0 67 Europe

最後の5行を表示させる。引数に整数を入れて表示行数を指定することも可能。

df.tail()
gdp con inv pop continent
country
Japan 5032 2675.0 1238.0 127 Asia
Korea 2157 942.0 705.0 51 Asia
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia
UK 2994 NaN NaN 68 Europe

次にdfの情報の特徴を確認するメソッド.info()を使ってみよう。

df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 10 entries, China to UK
Data columns (total 5 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   gdp        10 non-null     int64  
 1   con        9 non-null      float64
 2   inv        8 non-null      float64
 3   pop        10 non-null     int64  
 4   continent  10 non-null     object 
dtypes: float64(2), int64(2), object(1)
memory usage: 480.0+ bytes

説明

  • <class 'pandas.core.frame.DataFrame'>

    • classとはデータ型と同じ意味である。

    • pandas.core.frame.DataFrameがデータ型名でありクラス名でもある。

    • 例えば、type(1)を実行するとint(整数型)というデータ型が表示するが,これはクラス名でもある。print(type(1))を実行すると<class 'int'>と表示され、クラス名もintであることが分かる。

  • Index: 10 entries, Chine to UK

    • 行のインデックスの情報

    • ChineからUKまでの10個のデータ

  • Data columns (total 5 columns):

    • 列の数(5つ)

  • gdp  10 non-null int64

    • 列インデックスは0

    • 10の非欠損値のデータ(non-nullとは非欠損値を意味)があり,欠損値なし(行数が10であり、10の非欠損値であるため,即ち,欠損値数は11-11=0

    • データ型はint64

  • con  9 non-null float64

    • 列インデックスは1

    • 9の非欠損値データがあり,欠損値数は1=10-9

    • データ型はfloat64

  • inv  8 non-null float64

    • 列インデックスは2

    • 8の非欠損値データがあり,欠損値数は2=10-8

    • データ型はfloat64

  • pop  10 non-null int64

    • 列インデックスは3

    • 10の非欠損値データがあり,欠損値数なし0=10-10

    • データ型はint64

  • continent   10 non-null object

    • 列インデックスは4

    • 10の非欠損値データがあり,欠損値数なし0=10-10

    • データ型はobject(文字列などのデータ型)

    • (注意)整数型や浮動小数点型に文字列が混ざっている場合にも列のデータ型はobjectになる

  • dtypes: float64(2), int64(2), object(1)

    • dfの列にどのようなのデータ型があるかを示す

    • float64int64が2列つずつ,文字列は1列

  • memory usage: 480.0+ bytes

    • メモリー使用量は約480.0バイト

データを読み込んだら必ずinfo()を使って欠損値の数や列のデータ型を確認するようにしよう。

また,データの統計的な特徴は次の.describe()メソッドでチェックできる。

df.describe()
gdp con inv pop
count 10.000000 9.000000 8.000000 10.000000
mean 5070.700000 2605.222222 2009.625000 328.800000
std 5817.610106 2579.061107 2959.228564 565.729578
min 477.000000 176.000000 135.000000 6.000000
25% 2233.500000 942.000000 682.250000 53.500000
50% 2972.000000 1598.000000 888.500000 67.500000
75% 4842.500000 2675.000000 1546.250000 116.250000
max 20118.000000 7796.000000 9137.000000 1434.000000
  • count:観測値の数

  • mean:平均

  • std:標準偏差

  • min:最小値

  • max:最大値

  • 25%:第1四分位数

  • 50%:第2四分位数(中央値)

  • 75%:第3四分位数

  • max:最大値

次のデータ属性.shapeを使ってdfの行と列の長さを確認することができる。返値はタプルで,(行の数,列の数)と解釈する。

df.shape
(10, 5)

返値はタプルなので,行数は以下で取得できる。

df.shape[0]
10

len()関数を使って行数を示すこともできる。

len(df)
10

DataFrameの構成要素#

DataFrameには様々な属性があるが、次の3点について説明する。

  • データ:df.to_numpy()もしくはdf.valuesで抽出できる。

  • 列ラベル:df.columnsで抽出できる。

  • 行ラベル:df.indexで抽出できる。

まずデータ自体を抽出してみよう。

df.to_numpy()
array([[20118, 7796.0, 9137.0, 1434, 'Asia'],
       [2950, 1598.0, 806.0, 67, 'Europe'],
       [4274, 2294.0, 971.0, 84, 'Europe'],
       [9142, 5936.0, 2471.0, 1366, 'Asia'],
       [2463, 1423.0, 614.0, 61, 'Europe'],
       [5032, 2675.0, 1238.0, 127, 'Asia'],
       [2157, 942.0, 705.0, 51, 'Asia'],
       [477, 176.0, 135.0, 6, 'Asia'],
       [1100, 607.0, nan, 24, 'Asia'],
       [2994, nan, nan, 68, 'Europe']], dtype=object)
type(df.to_numpy())
numpy.ndarray

これで分かることは,メインのデータの部分はNumPyndarrayn次元array)であることだ。即ち,PandasNumPyに基づいて構築されており,データ値の計算などはarrayが裏で動いているということである。Pandasは行と列のラベルを追加し,より直感的に使えるように拡張しているのである。

次に列ラベルを取り出してみる。

df.columns
Index(['gdp', 'con', 'inv', 'pop', 'continent'], dtype='object')

dtype='object'から列ラベルに使われているデータ型(dtype)はオブジェクト型(object)だとわかる。

  • 上でも簡単に説明したが、オブジェクト型とは文字型を含む「その他」のデータ型と理解すれば良いだろう。

  • df.columnsの戻り値にあるdtype='object'df.to_numpy()の戻り値にあるdtype=objectは同じ意味。

列ラベル自体のデータ型(もしくは、クラス名)は次のコードで調べることができる。

type(df.columns)
pandas.core.indexes.base.Index

dir()もしくはpy4macro.see()で調べると多くのメソッドや属性が確認できるが,その中に.tolist()が含まれており,これを使うことにより列ラベルをリストに変換することができる。

df_columns = df.columns.tolist()
df_columns
['gdp', 'con', 'inv', 'pop', 'continent']

行ラベルについても同じことができる。

df.index
Index(['China', 'France', 'Germany', 'India', 'Italy', 'Japan', 'Korea',
       'Singapore', 'Taiwan', 'UK'],
      dtype='object', name='country')

行ラベルの各要素のデータ型objectは文字列を意味している。列countryを行ラベルに指定したが,行ラベル名を削除したのでname=''となっている。行ラベル自体(もしくは全体)のデータ型(クラス名)は

type(df.index)
pandas.core.indexes.base.Index

であり,ラベルをリストとして抽出することもできる。

df_index = df.index.tolist()
df_index
['China',
 'France',
 'Germany',
 'India',
 'Italy',
 'Japan',
 'Korea',
 'Singapore',
 'Taiwan',
 'UK']

要素の抽出#

説明#

NumPyarrayの場合,[,]を使い要素を抽出した。Pandasの場合,様々な抽出方法があるが,覚えやすく少しでも間違いの可能性を減らすために,そして可読性向上のためにarrayに対応する以下の2つの方法を使うことにする。

  • ラベルを使う方法:.loc[,]

  • インデックスを使う方法:.iloc[,](これはarray[ ]と同じと考えて良い)

1つ目のlocはラベルのlocationと覚えよう。2つ目はのilociはインデックス(index)のiであり,index locationという意味である。使い方はarrayの場合と基本的に同じである。

  • ,の左は行,右は列を表す。

  • 行または列を連続して選択する(slicing)場合は:を使う。(start:end

    • :の左右を省略する場合は,「全て」という意味になる。

    • :の左を省略すると「最初から」という意味になる。

    • :の右を省略すると「最後まで」という意味になる。

    • .loc[,]の場合,endを含む。(要注意!)

    • .iloc[,]の場合,endは含まず,その1つ前のインデックスまでが含まれる。

  • ,の右に書く:は省略可能であるが省略しないことを推奨する。

「特例」として.loc[,].iloc[,]以外に

  • ラベルと[]だけを使いを選択する方法

も説明する。

Warning

  • .loc[,]の場合,endを含む。(要注意!)

  • .iloc[,]の場合,endは含まず,その1つ前のインデックスまでが含まれる。

.loc[,](ラベル使用)#

1つの行をSeriesとして抽出

df.loc['Japan',:]
gdp            5032
con          2675.0
inv          1238.0
pop             127
continent      Asia
Name: Japan, dtype: object

1つの行をDataFrameとして抽出

df.loc[['Japan'],:]
gdp con inv pop continent
country
Japan 5032 2675.0 1238.0 127 Asia

SeriesDataFrameを比べると、表示が異なることが分かると思う。コードには次の違いがある。

  • DataFrameで抽出する場合、:の左側に[]を使う。

  • Seriesで抽出する場合、:の左側に[]を使わない。

複数行を抽出

df.loc[['Japan', 'UK'],:]
gdp con inv pop continent
country
Japan 5032 2675.0 1238.0 127 Asia
UK 2994 NaN NaN 68 Europe

上のコードでは、:の左側にリストが使われている。次のコードでも同じ結果となる。

rows = ['Japan', 'UK']  #1

df.loc[rows,:]          #2
gdp con inv pop continent
country
Japan 5032 2675.0 1238.0 127 Asia
UK 2994 NaN NaN 68 Europe

#1rowsはリストであり、#2:の左側にリストが使われていることが分かる。

複数行を連続抽出(slicing)

df.loc['France':'Japan',:]
gdp con inv pop continent
country
France 2950 1598.0 806.0 67 Europe
Germany 4274 2294.0 971.0 84 Europe
India 9142 5936.0 2471.0 1366 Asia
Italy 2463 1423.0 614.0 61 Europe
Japan 5032 2675.0 1238.0 127 Asia

スライシングの場合、:の左右に[]は使わない。

1つの列をSeriesとして抽出

df.loc[:,'gdp']
country
China        20118
France        2950
Germany       4274
India         9142
Italy         2463
Japan         5032
Korea         2157
Singapore      477
Taiwan        1100
UK            2994
Name: gdp, dtype: int64

Seriesで抽出するので、:の右に[]は使わない。

1つの列をDataFrameとして抽出

df.loc[:,['gdp']]
gdp
country
China 20118
France 2950
Germany 4274
India 9142
Italy 2463
Japan 5032
Korea 2157
Singapore 477
Taiwan 1100
UK 2994

DataFrameとして抽出するために、:の右に[]を使っている。

複数列を抽出

df.loc[:,['gdp','pop']]
gdp pop
country
China 20118 1434
France 2950 67
Germany 4274 84
India 9142 1366
Italy 2463 61
Japan 5032 127
Korea 2157 51
Singapore 477 6
Taiwan 1100 24
UK 2994 68

次のようにしても同じ結果となる。

cols = ['gdp','pop']

df.loc[:,cols]
gdp pop
country
China 20118 1434
France 2950 67
Germany 4274 84
India 9142 1366
Italy 2463 61
Japan 5032 127
Korea 2157 51
Singapore 477 6
Taiwan 1100 24
UK 2994 68

複数列を連続抽出(slicing)

df.loc[:,'con':'pop']
con inv pop
country
China 7796.0 9137.0 1434
France 1598.0 806.0 67
Germany 2294.0 971.0 84
India 5936.0 2471.0 1366
Italy 1423.0 614.0 61
Japan 2675.0 1238.0 127
Korea 942.0 705.0 51
Singapore 176.0 135.0 6
Taiwan 607.0 NaN 24
UK NaN NaN 68

.iloc[](位置番号を使用)#

1つの行をSeriesとして抽出

df.iloc[5,:]
gdp            5032
con          2675.0
inv          1238.0
pop             127
continent      Asia
Name: Japan, dtype: object

1つの行をDataFrameとして抽出

df.iloc[[5],:]
gdp con inv pop continent
country
Japan 5032 2675.0 1238.0 127 Asia

複数行を抽出

df.iloc[[1,5],:]
gdp con inv pop continent
country
France 2950 1598.0 806.0 67 Europe
Japan 5032 2675.0 1238.0 127 Asia

複数行を連続抽出(slicing)

df.iloc[1:6,:]
gdp con inv pop continent
country
France 2950 1598.0 806.0 67 Europe
Germany 4274 2294.0 971.0 84 Europe
India 9142 5936.0 2471.0 1366 Asia
Italy 2463 1423.0 614.0 61 Europe
Japan 5032 2675.0 1238.0 127 Asia

1つの列をSeriesとして抽出

df.iloc[:,1]
country
China        7796.0
France       1598.0
Germany      2294.0
India        5936.0
Italy        1423.0
Japan        2675.0
Korea         942.0
Singapore     176.0
Taiwan        607.0
UK              NaN
Name: con, dtype: float64

1つの列をDataFrameとして抽出

df.iloc[:,[1]]
con
country
China 7796.0
France 1598.0
Germany 2294.0
India 5936.0
Italy 1423.0
Japan 2675.0
Korea 942.0
Singapore 176.0
Taiwan 607.0
UK NaN

複数列を選択

df.iloc[:,[1,3]]
con pop
country
China 7796.0 1434
France 1598.0 67
Germany 2294.0 84
India 5936.0 1366
Italy 1423.0 61
Japan 2675.0 127
Korea 942.0 51
Singapore 176.0 6
Taiwan 607.0 24
UK NaN 68

次のコードでも同じ結果となる。

cols = [1, 3]

df.iloc[:,cols]
con pop
country
China 7796.0 1434
France 1598.0 67
Germany 2294.0 84
India 5936.0 1366
Italy 1423.0 61
Japan 2675.0 127
Korea 942.0 51
Singapore 176.0 6
Taiwan 607.0 24
UK NaN 68

複数列を連続抽出(slicing)

df.iloc[:,1:3]
con inv
country
China 7796.0 9137.0
France 1598.0 806.0
Germany 2294.0 971.0
India 5936.0 2471.0
Italy 1423.0 614.0
Japan 2675.0 1238.0
Korea 942.0 705.0
Singapore 176.0 135.0
Taiwan 607.0 NaN
UK NaN NaN

[]で列の選択(ラベル使用)#

1つの列をSeriesとして抽出

df['gdp']
country
China        20118
France        2950
Germany       4274
India         9142
Italy         2463
Japan         5032
Korea         2157
Singapore      477
Taiwan        1100
UK            2994
Name: gdp, dtype: int64

1つの列をDataFrameとして抽出

df[['gdp']]
gdp
country
China 20118
France 2950
Germany 4274
India 9142
Italy 2463
Japan 5032
Korea 2157
Singapore 477
Taiwan 1100
UK 2994

上の2つのコードの違いに注意しよう。

  • DataFrameで抽出する場合、角括弧が二重になっている。即ち、[[]]

  • Seriesで抽出する場合、角括弧が二重になっていない。即ち、[]

次の様に考えよう。[]を使って列を抽出する場合、

  • []の中にラベル名をそのまま書くとSeriesとして抽出される。

  • []の中にラベル名をリストとして書くとDataFrameとして抽出される。

複数列を選択

df[['gdp','pop']]
gdp pop
country
China 20118 1434
France 2950 67
Germany 4274 84
India 9142 1366
Italy 2463 61
Japan 5032 127
Korea 2157 51
Singapore 477 6
Taiwan 1100 24
UK 2994 68

ある条件の下で行の抽出#

1つの条件の場合#

例1:GDPが1200未満の行の抽出#

まず条件を作る。

df['gdp'] < 1200
country
China        False
France       False
Germany      False
India        False
Italy        False
Japan        False
Korea        False
Singapore     True
Taiwan        True
UK           False
Name: gdp, dtype: bool

この条件では,GDPが100未満の行はTrue,以上の行はFalseとなる。この条件をcondというの変数に割り当てる。()を省いても良いが,ある方が分かりやすいだろう。

cond = (df['gdp'] < 1200)

cond.loc[,]の引数とすることにより,Trueの行だけを抽出できる。(注意:condを使ってを抽出しようとしているので,の左側に書く。)

df.loc[cond,:]
gdp con inv pop continent
country
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia

この条件の下でinvだけを抽出したい場合

  • df.loc[cond,'inv']

とする。

Warning

以下のように抽出を連続ですることも可能だが,避けるように!

  • df.loc[cond,:]['inv']

  • df.loc[cond,:].loc[:,'inv']

例2:continentAsiaの行を抽出#

cond = (df.loc[:,'continent'] == 'Asia')

df.loc[cond,:]
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
India 9142 5936.0 2471.0 1366 Asia
Japan 5032 2675.0 1238.0 127 Asia
Korea 2157 942.0 705.0 51 Asia
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia

複数条件の場合#

例3#

以下の条件の両方が満たされる場合:

  • gdp5000以上

  • inv2000以下

それぞれの条件を作成する。

df
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
France 2950 1598.0 806.0 67 Europe
Germany 4274 2294.0 971.0 84 Europe
India 9142 5936.0 2471.0 1366 Asia
Italy 2463 1423.0 614.0 61 Europe
Japan 5032 2675.0 1238.0 127 Asia
Korea 2157 942.0 705.0 51 Asia
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia
UK 2994 NaN NaN 68 Europe
cond1 = (df['gdp'] >= 5000)
cond2 = (df['inv'] <= 2000)

2つの条件が同時に満たされる条件を作成する。

cond = (cond1 & cond2)

<注意>

  • &を使っている。理由は,cond1cond2のそれぞれの行の要素を比較して真偽値から構成されるSeriesを返すためである。従って,一つの値をもう一つの値と比べて真偽値を計算するandを使うとエラーが発生する。

  • DataFrameSeries&を使う場合は,比較コードを()の中に入れることを推奨する。&は優先されて計算されるためである。(上の例では問題は発生しなし。)

condを引数に使い行を抽出する。

df.loc[cond, :]
gdp con inv pop continent
country
Japan 5032 2675.0 1238.0 127 Asia

例4#

以下の条件のどちらかが満たされる場合:

  • gdp5000以上

  • con2000以下

cond1 = (df['gdp'] >= 5000)
cond2 = (df['con'] <= 2000)
cond = (cond1 | cond2)

df.loc[cond, :]
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
France 2950 1598.0 806.0 67 Europe
India 9142 5936.0 2471.0 1366 Asia
Italy 2463 1423.0 614.0 61 Europe
Japan 5032 2675.0 1238.0 127 Asia
Korea 2157 942.0 705.0 51 Asia
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia

<注意>

  • |を使っている。理由は,cond1cond2のそれぞれの行の要素を比較して真偽値から構成されるSeriesを返すためである。従って,一つの値をもう一つの値と比べて真偽値を計算するorを使うとエラーが発生する。

  • DataFrameSeries|を使う場合は,比較コードを()の中に入れることを推奨する。|は優先されて計算されるためである。(上の例では問題は発生しなし。)

<コメント>

  • 上の例では,&and|orの役割を果たしている。同様に~not(真偽値の反転)の代わりに使われる。

  • DataFrameSeries~を使う場合は,コードを()の中に入れることを推奨する。~は優先されて計算されるためである。

例5#

以下の条件のどちらかが満たされ

  • gdp5000以上

  • con2000以下

かつ以下の条件も同時に満たされる場合:

  • continentAsiaと等しい

cond1 = (df['gdp'] >= 5000)
cond2 = (df['con'] <= 2000)
cond3 = (df['continent'] == 'Asia')
cond = ((cond1 | cond2) & cond3)

df.loc[cond, :]
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
India 9142 5936.0 2471.0 1366 Asia
Japan 5032 2675.0 1238.0 127 Asia
Korea 2157 942.0 705.0 51 Asia
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia

query()#

query()というメソッドを使うと、文字列を使い行の抽出コードを書くことができる。これにより直感的なコード書くことが可能となる。

例1の場合:#

df.query('gdp < 1200')
gdp con inv pop continent
country
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia

例2の場合#

df.query('continent == "Asia"')
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
India 9142 5936.0 2471.0 1366 Asia
Japan 5032 2675.0 1238.0 127 Asia
Korea 2157 942.0 705.0 51 Asia
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia

例3の場合#

df.query('(gdp >= 5000) & (inv <= 2000)')
gdp con inv pop continent
country
Japan 5032 2675.0 1238.0 127 Asia

例4の場合#

df.query('(gdp >= 5000) | (con <= 2000)')
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
France 2950 1598.0 806.0 67 Europe
India 9142 5936.0 2471.0 1366 Asia
Italy 2463 1423.0 614.0 61 Europe
Japan 5032 2675.0 1238.0 127 Asia
Korea 2157 942.0 705.0 51 Asia
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia

例5の場合#

df.query('(gdp >= 5000 | con <= 2000) & (continent == "Asia")')
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
India 9142 5936.0 2471.0 1366 Asia
Japan 5032 2675.0 1238.0 127 Asia
Korea 2157 942.0 705.0 51 Asia
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia

Tip

dfにない変数で条件を設定する場合@が必要になる。例えば,変数zという変数があるとしよう。

z = 100

変数zの値に基づいて行の抽出をする場合は次のようにする。

df.query('gdp < @z')

gdp con inv pop continent
country
Singapore 477 176.0 135.0 6 Asia
Taiwan 1100 607.0 NaN 24 Asia

Warning

数字で始まる列ラベルに.query()を使うとエラーが発生するため,列ラベルを変更する必要がある。列ラベルを変更できない場合は異なる方法を使うように。

列と行の追加と削除#

列の追加 [ ]#

[]に列ラベルを使って列を抽出することができるが,[]は列の追加にも使えるので,ここではその使い方を説明する。まず,全ての行が1.0となる列を作成するとしよう。その場合,以下のようにする。

df['Intercept'] = 1.0
df.head(2)
gdp con inv pop continent Intercept
country
China 20118 7796.0 9137.0 1434 Asia 1.0
France 2950 1598.0 806.0 67 Europe 1.0

次の例では既存の列から新たな列を作成する。まず1人当たりGDPの計算を計算し,それを変数gdp_pcに割り当てる。

gdp_pc = df['gdp'] / df['pop']

gdp_pc.head()
country
China      14.029289
France     44.029851
Germany    50.880952
India       6.692533
Italy      40.377049
dtype: float64

これはSeriesであり,gdp_pcとしてdfに追加する。

df['gdp_pc'] = gdp_pc
df.head(2)
gdp con inv pop continent Intercept gdp_pc
country
China 20118 7796.0 9137.0 1434 Asia 1.0 14.029289
France 2950 1598.0 806.0 67 Europe 1.0 44.029851

列の追加 .loc[,]#

.loc[]は行と列の抽出に使ったが,追加にも使える。[]と同じと考えれば良いだろう。次の例ではpopを2倍した列を追加している。

df.loc[:,'2pop'] = 2 * df['pop']
df.head(3)
gdp con inv pop continent Intercept gdp_pc 2pop
country
China 20118 7796.0 9137.0 1434 Asia 1.0 14.029289 2868
France 2950 1598.0 806.0 67 Europe 1.0 44.029851 134
Germany 4274 2294.0 971.0 84 Europe 1.0 50.880952 168

列の削除 del#

del df['2pop']

ここでdelはdeleteの略。

df.head(2)
gdp con inv pop continent Intercept gdp_pc
country
China 20118 7796.0 9137.0 1434 Asia 1.0 14.029289
France 2950 1598.0 806.0 67 Europe 1.0 44.029851

2popが削除されている。

列の削除 drop()#

DataFrameには.drop()というメソッドが用意されているので,それを使うことも可能である。

下のコードの説明:

  • 引数

    • 第1引数(位置引数):削除する列ラベルのリスト(一つの列のみを指定する場合はリストではなく、そのまま列ラベルを書いても可)

    • 第2引数(キーワード引数):axis=columnsもしくはaxis=11は列を表しし、0は行を表す)

  • df.drop()はコピーを作るだけなので,元のdfを上書きしたい場合は次のどちらかが必要となる。

    • dfに再割り当てする。

    • オプションinplace=True(デフォルトはFalse)を追加する。

df = df.drop(['Intercept','gdp_pc'], axis='columns')

# df.drop('Intercept', axis='columns', inplace=True)
df.head(2)
gdp con inv pop continent
country
China 20118 7796.0 9137.0 1434 Asia
France 2950 1598.0 806.0 67 Europe

行の追加 .loc[,]#

.loc[,]はh行と列の抽出,そして列の追加に使ったが,行の追加にも使える。

df.loc['US',:] = [20566, 14462, 4557, 329, 'North America']
df.tail(3)
gdp con inv pop continent
country
Taiwan 1100.0 607.0 NaN 24.0 Asia
UK 2994.0 NaN NaN 68.0 Europe
US 20566.0 14462.0 4557.0 329.0 North America

上の例では,最初の4つの要素は整数として入力されたが,dfの中では浮動小数点に変換されている。

行の削除 drop()#

.drop()は列を削除する際に紹介したが、行の削除にも使えるメソッドである。

  • 引数

    • 第1引数(位置引数):削除する行ラベルのリスト(一つの行のみを指定する場合はリストではなく、そのまま行ラベルを書いても可)

    • 第2引数(キーワード引数):axis=rowsもしくはaxis=01は列を表しし、0は行を表す)

  • df.drop()はコピーを作るだけなので,元のdfを上書きしたい場合は次のどちらかが必要となる。

    • dfに再割り当てする。

    • オプションinplace=True(デフォルトはFalse)を追加する。

df = df.drop('US', axis='rows')

# df.drop('US', axis=0, inplace=True)
df.tail()
gdp con inv pop continent
country
Japan 5032.0 2675.0 1238.0 127.0 Asia
Korea 2157.0 942.0 705.0 51.0 Asia
Singapore 477.0 176.0 135.0 6.0 Asia
Taiwan 1100.0 607.0 NaN 24.0 Asia
UK 2994.0 NaN NaN 68.0 Europe

USの行が削除されている。

欠損値の扱い#

Pandasでは欠損値はNaN(Not a Number)や<NA>(Not Availabel)と表示されるが,nullと呼んだりもする。

欠損値の確認#

欠損値があるかどうかの確認は,df.info()でもできるが,以下のメソッドを組み合わせることでも可能である。

  • isna():(is it na?の略)それぞれの要素についてNaNの場合Trueを,そうでない場合はFalseを返す。(DataFrameの全ての要素がTrue/Falseとなる。)

  • sum()dfの上から下に(rows)を縦断して,それぞれの列の中にあるTrue数える。

    • デフォルトで引数axis='rows'が指定されている。

    • 引数値rowsは複数!(0でも可)

  • sum(axis='columns')dfの左から右に(columns)を横断して,それぞれの行の中にあるTrueを数える。

    • 引数値columnsは複数!(1でも可)

(注意)sum()axisは「行を縦断」か「列を横断」かを指定する。

df.isna().sum()
gdp          0
con          1
inv          2
pop          0
continent    0
dtype: int64

invconNaNがあることがわかる。axis='columns'を設定するとNaNがある行を確認できる。

df.isna().sum(axis='columns')
country
China        0
France       0
Germany      0
India        0
Italy        0
Japan        0
Korea        0
Singapore    0
Taiwan       1
UK           2
dtype: int64

NaNがある行だけを抽出したい場合がある。その場合はメソッドany()が役に立つ。

  • any()dfの上から下に行(rows)を縦断して,それぞれの列の中で一つ以上Trueがある場合にはTrueを,一つもない場合はFalseを返す。

    • デフォルトで引数axis='rows'が指定されている。

    • 引数値rowsは複数!(0でも可)

  • any(axis='columns'):dfの左から右に列(columns)を横断して,それぞれの行の中で一つ以上Trueがある場合にはTrueを,一つもない場合はFalseを返す。

    • 引数値columnsは複数!(1でも可)

(注意)any()axisは「行を縦断」か「列を横断」かを指定する。

cond = df.isna().any(axis='columns')
df.loc[cond,:]
gdp con inv pop continent
country
Taiwan 1100.0 607.0 NaN 24.0 Asia
UK 2994.0 NaN NaN 68.0 Europe

これでNaNがある行だけを抽出できた。

欠損値がある行の削除#

欠損値がある全ての行を削除する。

df.dropna()
gdp con inv pop continent
country
China 20118.0 7796.0 9137.0 1434.0 Asia
France 2950.0 1598.0 806.0 67.0 Europe
Germany 4274.0 2294.0 971.0 84.0 Europe
India 9142.0 5936.0 2471.0 1366.0 Asia
Italy 2463.0 1423.0 614.0 61.0 Europe
Japan 5032.0 2675.0 1238.0 127.0 Asia
Korea 2157.0 942.0 705.0 51.0 Asia
Singapore 477.0 176.0 135.0 6.0 Asia

TaiwanUKが削除されている。

このメソッドは,欠損値を削除するとどうなるかを示すだけでありdf自体は影響は受けない。df自体からNaNがある行を削除する場合はinplace=Trueのオプション(デフォルトではFalseになっている)を加えて

df.dropna(inplace=True)

とするか,削除後のdfdf自体に再割り当てする。

df = df.dropna()

また,ある列でNaNがある行のみを削除する場合は,引数subsetを使う。 次のコードでは、conNaNがある行のみを削除している。

df.dropna(subset=['con'])
gdp con inv pop continent
country
China 20118.0 7796.0 9137.0 1434.0 Asia
France 2950.0 1598.0 806.0 67.0 Europe
Germany 4274.0 2294.0 971.0 84.0 Europe
India 9142.0 5936.0 2471.0 1366.0 Asia
Italy 2463.0 1423.0 614.0 61.0 Europe
Japan 5032.0 2675.0 1238.0 127.0 Asia
Korea 2157.0 942.0 705.0 51.0 Asia
Singapore 477.0 176.0 135.0 6.0 Asia
Taiwan 1100.0 607.0 NaN 24.0 Asia

(注意)オプションsubset=には削除する列が1つであっても、[]を使いリストとして指定する。

並び替え#

行の並び替えにはメソッド.sort_values()を使う。引数に列ラベルを指定することにより、その列を基準に行を昇順に並び替える。

dfgdpの昇順に並び替えてみよう。

df.sort_values('gdp').head()
gdp con inv pop continent
country
Singapore 477.0 176.0 135.0 6.0 Asia
Taiwan 1100.0 607.0 NaN 24.0 Asia
Korea 2157.0 942.0 705.0 51.0 Asia
Italy 2463.0 1423.0 614.0 61.0 Europe
France 2950.0 1598.0 806.0 67.0 Europe

降順にする場合は、引数ascending=Falseを付け加える(デフォルトではascending=Trueとなっている)。

df.sort_values('gdp', ascending=False).head()
gdp con inv pop continent
country
China 20118.0 7796.0 9137.0 1434.0 Asia
India 9142.0 5936.0 2471.0 1366.0 Asia
Japan 5032.0 2675.0 1238.0 127.0 Asia
Germany 4274.0 2294.0 971.0 84.0 Europe
UK 2994.0 NaN NaN 68.0 Europe

複数の列を基準に並び替えする場合は、引数ascending=に真偽値をリストとして指定する。

df.sort_values(['continent','gdp'], ascending=[True,False])
gdp con inv pop continent
country
China 20118.0 7796.0 9137.0 1434.0 Asia
India 9142.0 5936.0 2471.0 1366.0 Asia
Japan 5032.0 2675.0 1238.0 127.0 Asia
Korea 2157.0 942.0 705.0 51.0 Asia
Taiwan 1100.0 607.0 NaN 24.0 Asia
Singapore 477.0 176.0 135.0 6.0 Asia
Germany 4274.0 2294.0 971.0 84.0 Europe
UK 2994.0 NaN NaN 68.0 Europe
France 2950.0 1598.0 806.0 67.0 Europe
Italy 2463.0 1423.0 614.0 61.0 Europe

ここではcontinentに従って先に並び替えられ,その後にgdpに従って並び替えられている。ascendingは昇順(True)か降順(False)かを指定する引数であり,['continent','gdp']ascending=['True','False']の順番が対応している。

行番号を振り直す#

データの読み込みとデータのチェックにおいてデータを読み込んだ後,直ぐに.set_index('year')を使い,列yearを行ラベルに設定した。もちろん,必ずしもそうする必要はなく,行番号(012,…)のままで作業をおこなっても全く問題ない。また行ラベルを設定した後に,行インデックスに戻したい場合もあるだろう。その場合には,メソッド.reset_index()を使うと,行のインデックスを0,1,2,..と振り直すことができる。dfを使うと次のようになる。

df.reset_index()
country gdp con inv pop continent
0 China 20118.0 7796.0 9137.0 1434.0 Asia
1 France 2950.0 1598.0 806.0 67.0 Europe
2 Germany 4274.0 2294.0 971.0 84.0 Europe
3 India 9142.0 5936.0 2471.0 1366.0 Asia
4 Italy 2463.0 1423.0 614.0 61.0 Europe
5 Japan 5032.0 2675.0 1238.0 127.0 Asia
6 Korea 2157.0 942.0 705.0 51.0 Asia
7 Singapore 477.0 176.0 135.0 6.0 Asia
8 Taiwan 1100.0 607.0 NaN 24.0 Asia
9 UK 2994.0 NaN NaN 68.0 Europe

行ラベルがなくなり、元の行ラベル名は削除していたので、列ラベルないまま新たな列として追加されている。列ラベルを追加したい場合は、以下で説明するが、次のようにすれば良い。

df = df.rename({'':'country'}, axis='columns')

ここで''は空の文字列を示す。一方で、元の行ラベルを列として追加したくない場合もある。その場合、reset_index()に引数drop=Trueを加えると良い。

df.reset_index(drop=True).head()
gdp con inv pop continent
0 20118.0 7796.0 9137.0 1434.0 Asia
1 2950.0 1598.0 806.0 67.0 Europe
2 4274.0 2294.0 971.0 84.0 Europe
3 9142.0 5936.0 2471.0 1366.0 Asia
4 2463.0 1423.0 614.0 61.0 Europe

.reset_index()は、変更した場合にどのように見えるかを表示しているだけなので、df自体を変更する場合はdfに再割り当てする必要がある。

列のラベルの変更#

列ラベルを変更する場合は、メソッド.rename()を使うと良いだろう。引数は次の形で設定する。

\[\text{.rename}\left(\text{columns=}辞書\right)\]

ここで「辞書」は次のルールで指定する。

  • key:元のラベル

  • value:新しいラベル

次のコードでは,dfpoppop_newに,idcontinent_newに変更している。

df.rename(columns={'pop':'pop_new','continent':'continent_new'}).head()
gdp con inv pop_new continent_new
country
China 20118.0 7796.0 9137.0 1434.0 Asia
France 2950.0 1598.0 806.0 67.0 Europe
Germany 4274.0 2294.0 971.0 84.0 Europe
India 9142.0 5936.0 2471.0 1366.0 Asia
Italy 2463.0 1423.0 614.0 61.0 Europe

ただし,再割り当てしないとdfは変更されないので注意しよう。即ち,上のコードではdfの行ラベルは変更されていない。

.rename()以外にも次のように変更することも可能である。

df.columns = ['gdp','inv','con','pop_new','continent_new']

このコードは

右辺の文字列のリストをdf.columnsに割り当てる

と読むことができる。このコードにより割り当てが完了し,dfは変更されることになる。dfを表示してみよう。

df.head(2)
gdp inv con pop_new continent_new
country
China 20118.0 7796.0 9137.0 1434.0 Asia
France 2950.0 1598.0 806.0 67.0 Europe

この方法が簡単そうだが、全ての列ラベルをリストとして準備する必要がある点である。列の数が多いと面倒なので,そういう場合は.rename()の方が使いやすいだろう。元の列ラベルに戻しておこう。

df = df.rename({'pop_new':'pop', 'continent_new':'continent'}, axis='columns')

便利な使うメソッド#

idには文字列があり,行のデータをカテゴリー別に分けていると考えることができる。メソッド.unique()を使うと,選択した列に重複したデータがある場合,ユニークなものだけを抽出できる。

df['continent'].unique()
array(['Asia', 'Europe'], dtype=object)

まな類似するメソッドに.nunique()があり,カテゴリー数を返す。

df['continent'].nunique()
2

関連するメソッドに.value_counts()がある。これを使うとカテゴリーの内訳を確認するすることができる。各カテゴリーに対応する行数(度数)を表示するには

df.loc[:,'continent'].value_counts()
continent
Asia      6
Europe    4
Name: count, dtype: int64

とする。引数normalize=Trueを追加すると,相対度数として表示できる。

df.loc[:,'continent'].value_counts(normalize=True)
continent
Asia      0.6
Europe    0.4
Name: proportion, dtype: float64

dfのように行数が少ないDataFrameの場合,これらのメソッドの有用性を見出すことは難しいが,何千行あるデータを扱っていると想像してみよう。そのような大きなデータを扱う場合は非常に重宝するメソッドだろう。

統計関連のメソッド#

時系列データ#

統計関連のメソッドを幾つか紹介するために日本の時系列データからなる.csvファイルを読み込むことにする。data2.csvここをクリックをマウスの右クリックすることによりダウンロードすることもできる。

jp = pd.read_csv('./data/data2.csv')
jp
year gdp con inv gov net_ex
0 2012 517928.400 295751.750 98682.200 127135.975 -3941.775
1 2013 528522.025 303606.600 100590.050 129984.100 -6006.850
2 2014 529656.425 300593.375 103200.725 131434.275 -5477.075
3 2015 538098.750 300107.925 108697.000 132249.450 -2955.575
4 2016 541964.600 298675.925 109195.975 134433.625 -340.550
5 2017 551205.775 301894.650 111970.275 134762.325 2800.425
6 2018 554349.275 302496.325 113151.350 136011.050 2901.725
7 2019 553019.775 300927.225 113488.750 138450.375 281.825
8 2020 527946.300 285155.350 104932.325 142151.400 -4601.625
9 2021 536811.775 288895.200 103434.900 143454.975 1222.325

数値は日本の四半期データを年平均(単位:10億円)にしている。

  • year:年

  • gdp:国内総生産(Gross Domestic Product)

  • con:消費(consumption)

  • inv:投資(investment)

  • gov:政府支出(government expenditure)

  • net_ex:純輸出(net exports)

yearを行ラベルに設定して説明を進めていくことにする。

jp = jp.set_index('year')
jp.head(2)
gdp con inv gov net_ex
year
2012 517928.400 295751.75 98682.20 127135.975 -3941.775
2013 528522.025 303606.60 100590.05 129984.100 -6006.850

メソッド#

<以下で説明する統計関連のメソッドに関する注意点>

  • 計算して意味のある列のみを選択すること。pandasのバージョンにもよるが、文字列型の列を計算に含めるとエラーが発生する。jpは全てが浮動小数点となるため、以下では全ての列について計算を行なう。

  • 数値に含まれる欠損値は無視されることになる。

上でも出てきたが.sum()は各列の合計を返す(axis='rows'がデフォルト)。

jp.sum()
gdp       5379503.100
con       2978104.325
inv       1067343.550
gov       1350067.550
net_ex     -16117.150
dtype: float64

各行の合計を計算したい場合は引数axis=1もしくはaxis='columns'を指定する。ここでcolumnsとなるのは列を横断すると覚えれば良いだろう。支出要素だけを合計してみよう。

jp.iloc[:,1:].sum(axis='columns')
year
2012    517628.150
2013    528173.900
2014    529751.300
2015    538098.800
2016    541964.975
2017    551427.675
2018    554560.450
2019    553148.175
2020    527637.450
2021    537007.400
dtype: float64

同じ使い方で次のメソッドが利用できる。

  • .max():最大値

  • .min():最小値

  • .mean():(算術)平均

  • .std():標準偏差

    • 引数ddof=1はデフォルトで1

    • std()はデフォルトで不偏標準偏差を計算する

  • .var():分散

    • 引数ddof=1はデフォルトで1

    • var()はデフォルトで不偏分散を計算する

次の2つも便利である。計算する際,欠損値は無視され,結果はDataFrameとして返される。

  • .cov():分散共分散行列

    • 引数ddof=1はデフォルトで1

    • cov()はデフォルトで不偏分散・共分散を計算する

  • .corr():相関係数

Warning

pandasnumpyでは,標準偏差,分散,共分散を計算する際の引数ddofのデフォルトの値が異なるので注意が必要!

  • DataFrameのメソッド

    • .std().var().cov()の全てでddof=1がデフォルト

  • numpyの関数

    • .std().var()ではddof=0がデフォルト

    • .cov()ではddof=1がデフォルト

母集団の標準偏差・分散・共分散の不偏推定量を計算するにはddof=1が必要!!

Warning

DataFrameのメソッド.cov().corr()は欠損値を自動的に無視する。従って,相関係数の場合,.corr()の結果と.std()を使う「手計算」の結果を比べる際は欠損値がどのように扱われているかに注意する必要がある。

例えば,.cov()を計算してみよう。

jp.cov()
gdp con inv gov net_ex
gdp 1.497548e+08 3.133412e+07 6.187068e+07 2.398800e+07 3.475253e+07
con 3.133412e+07 3.773237e+07 1.138839e+07 -2.010831e+07 2.691150e+06
inv 6.187068e+07 1.138839e+07 2.814845e+07 9.745975e+06 1.342318e+07
gov 2.398800e+07 -2.010831e+07 9.745975e+06 2.702392e+07 7.750688e+06
net_ex 3.475253e+07 2.691150e+06 1.342318e+07 7.750688e+06 1.150046e+07

対角成分は分散であり,その他は行と列のラベルに対応する共分散となる。.corr()も同じ位置関係となる。

jp.corr()
gdp con inv gov net_ex
gdp 1.000000 0.416841 0.952944 0.377076 0.837411
con 0.416841 1.000000 0.349444 -0.629715 0.129188
inv 0.952944 0.349444 1.000000 0.353365 0.746054
gov 0.377076 -0.629715 0.353365 1.000000 0.439651
net_ex 0.837411 0.129188 0.746054 0.439651 1.000000

また変数の変化率(成長率)を計算するには.pct_change()が便利である(percent changeの略)。\(x_t\)を変数とすると、次式に従って毎期ごとの変化率を返すことになる。

\[ \text{変化率}_t=\frac{x_{t}-x_{t-1}}{x_{t-1}} \]
jp.pct_change()
gdp con inv gov net_ex
year
2012 NaN NaN NaN NaN NaN
2013 0.020454 0.026559 0.019333 0.022402 0.523895
2014 0.002146 -0.009925 0.025954 0.011157 -0.088195
2015 0.015939 -0.001615 0.053258 0.006202 -0.460373
2016 0.007184 -0.004772 0.004591 0.016516 -0.884777
2017 0.017051 0.010777 0.025407 0.002445 -9.223242
2018 0.005703 0.001993 0.010548 0.009266 0.036173
2019 -0.002398 -0.005187 0.002982 0.017935 -0.902877
2020 -0.045339 -0.052411 -0.075394 0.026732 -17.327952
2021 0.016792 0.013115 -0.014270 0.009170 -1.265629

2011年が無いため,2012年の成長率は欠損値となっている。%表示するのであれば

100 * jp.pct_change()

とすれば良いだろう。

.pct_change().mean()を続けて書くと簡単に(算術)平均成長率を計算することができる。

jp.pct_change().mean()
gdp       0.004170
con      -0.002385
inv       0.005823
gov       0.013536
net_ex   -3.288109
dtype: float64

Seriesについて#

Seriesについて簡単に説明する。Seriesはスプレッド・シートから1つの行または列を取り出したようなデータとイメージすれば良いだろう。Seriesには行と列を区別しないが、SeriesのインデックスはDataFrameのインデックスと同じと考えて良いだろう。ただし、以下では次の表現を使う。

  • インデックスが位置番号(012)をインデックス番号と呼ぶ。

  • インデックスがラベルの場合は、単にラベルと呼ぶ。

まずdfの列gdpからSeriesを作ってみよう。

s = df.loc[:,'gdp']
s
country
China        20118.0
France        2950.0
Germany       4274.0
India         9142.0
Italy         2463.0
Japan         5032.0
Korea         2157.0
Singapore      477.0
Taiwan        1100.0
UK            2994.0
Name: gdp, dtype: float64

Note

df.loc[:,['gdp']]

もしくは

df[['gdp']]

で列を抽出するとDataFrameが返される。また次のようにsのメソッド.to_frame()を使うとSeriesDataFrameに変換できるので、覚えておくと便利かも知れない。

s.to_frame()

新たに列ラベルを設定する場合は

s.to_frame('新たな列ラベル')

とする。

dfの行ラベルが整数型であり、それがsのインデックスに使われている。 また、gdpsの名前(Name)として使われている。

構成要素#

この例を使いSeriesの構成要素について説明する。

  • データ:s.to_numpy()もしくはs.valuesで抽出可能

  • Series名:s.nameで抽出可能

    • dfの列ラベルから引き継がれているが、空の場合もある。

  • インデックス(番号もしくはラベル):s.indexで抽出可能

    • 上の例ではdfの行ラベルから引き継がれている

    • インデックス番号であれば、0,1,2,…と表示される。

  • インデックス名:s.index.nameで抽出可能

    • dfの行ラベル名から引き継がれていが、空の場合もある。

まずデータ自体を抽出する。

s.to_numpy()
array([20118.,  2950.,  4274.,  9142.,  2463.,  5032.,  2157.,   477.,
        1100.,  2994.])
type(s.to_numpy())
numpy.ndarray

Series名の抽出

s.name
'gdp'

インデックスの抽出

s.index
Index(['China', 'France', 'Germany', 'India', 'Italy', 'Japan', 'Korea',
       'Singapore', 'Taiwan', 'UK'],
      dtype='object', name='country')

dtype='object'とあるが、ラベルは文字列であることがわかる。

要素の抽出#

要素の抽出はラベルを使う方法とインデックス番号を使う方法がある。ラベルを使う場合を考えてみよう。DataFrameと同様に.loc[]を使う事ができる。違いは、Seriesの場合、行と列の区別がないのでラベルだけを指定すれば良い。

s.loc['Japan']
5032.0

複数の場合はラベルをリストとする。

s.loc[['Japan','Korea','UK']]
country
Japan    5032.0
Korea    2157.0
UK       2994.0
Name: gdp, dtype: float64

ラベルを使う場合,.locを省略することができる。辞書と同じような形となる。

s['Japan']
5032.0
s[['Japan','Korea','UK']]
country
Japan    5032.0
Korea    2157.0
UK       2994.0
Name: gdp, dtype: float64

次にインデックス番号を使う要素の抽出方法を紹介しよう。DataFrameと同様に.iloc[]を使う。

s.iloc[5]
5032.0

複数のインデックスを使うことも可能である。

s.iloc[[1,3,5]]
country
France    2950.0
India     9142.0
Japan     5032.0
Name: gdp, dtype: float64

またスライシングも同様にできる。

s.iloc[5:-2]
country
Japan        5032.0
Korea        2157.0
Singapore     477.0
Name: gdp, dtype: float64

Warning

Pandasのバージョンによっては.ilocを省略してs[1]のようにして要素にアクセスできるかも知れないが、バージョンが進むと使えなくなるので注意しよう。

統計学関連メソッド#

SeriesにもDataFrameと同じような統計学関連メソッドが実装されているので,py4macro.see()で調べてみよう。

py4macro.see(s)
.China              .France             .Germany            .India
.Italy              .Japan              .Korea              .Singapore
.T                  .Taiwan             .UK                 .abs
.add                .add_prefix         .add_suffix         .agg
.aggregate          .align              .all                .any
.apply              .argmax             .argmin             .argsort
.array              .asfreq             .asof               .astype
.at                 .at_time            .attrs              .autocorr
.axes               .backfill           .between            .between_time
.bfill              .bool               .case_when          .clip
.combine            .combine_first      .compare            .convert_dtypes
.copy               .corr               .count              .cov
.cummax             .cummin             .cumprod            .cumsum
.describe           .diff               .div                .divide
.divmod             .dot                .drop               .drop_duplicates
.droplevel          .dropna             .dtype              .dtypes
.duplicated         .empty              .eq                 .equals
.ewm                .expanding          .explode            .factorize
.ffill              .fillna             .filter             .first
.first_valid_index  .flags              .floordiv           .ge
.get                .groupby            .gt                 .hasnans
.head               .hist               .iat                .idxmax
.idxmin             .iloc               .index              .infer_objects
.info               .interpolate        .is_monotonic_decreasing  .is_monotonic_increasing
.is_unique          .isin               .isna               .isnull
.item               .items              .keys               .kurt
.kurtosis           .last               .last_valid_index   .le
.list               .loc                .lt                 .map
.mask               .max                .mean               .median
.memory_usage       .min                .mod                .mode
.mul                .multiply           .name               .nbytes
.ndim               .ne                 .nlargest           .notna
.notnull            .nsmallest          .nunique            .pad
.pct_change         .pipe               .plot               .pop
.pow                .prod               .product            .quantile
.radd               .rank               .ravel              .rdiv
.rdivmod            .reindex            .reindex_like       .rename
.rename_axis        .reorder_levels     .repeat             .replace
.resample           .reset_index        .rfloordiv          .rmod
.rmul               .rolling            .round              .rpow
.rsub               .rtruediv           .sample             .searchsorted
.sem                .set_axis           .set_flags          .shape
.shift              .size               .skew               .sort_index
.sort_values        .squeeze            .std                .struct
.sub                .subtract           .sum                .swapaxes
.swaplevel          .tail               .take               .to_clipboard
.to_csv             .to_dict            .to_excel           .to_frame
.to_hdf             .to_json            .to_latex           .to_list
.to_markdown        .to_numpy           .to_period          .to_pickle
.to_sql             .to_string          .to_timestamp       .to_xarray
.transform          .transpose          .truediv            .truncate
.tz_convert         .tz_localize        .unique             .unstack
.update             .value_counts       .values             .var
.view               .where              .xs

この中に

  • .mean:平均

  • .std:標準偏差

  • .var:分散

  • .sum:合計

を計算するメソッドが含まれているのが確認できる。

グループ計算#

再度dfを使って説明する。表示してみよう。

df.head()
gdp inv con pop continent
country
China 20118.0 7796.0 9137.0 1434.0 Asia
France 2950.0 1598.0 806.0 67.0 Europe
Germany 4274.0 2294.0 971.0 84.0 Europe
India 9142.0 5936.0 2471.0 1366.0 Asia
Italy 2463.0 1423.0 614.0 61.0 Europe

dfのメソッド.mean()を使うと列の平均を簡単に計算できることは説明した。id以外の列を取り出して計算してみよう。

df.iloc[:,:-1].mean()
gdp    5070.700000
inv    2605.222222
con    2009.625000
pop     328.800000
dtype: float64

一方で,列continentにはAsiaEuropeがあり,dfを2つのグループに分けることができる。ここで問題になるのは,グループ別に列の平均を計算したい場合である。まず考えられる方法はAsiaグループだけを抽出して平均を計算し,同じようにEuropeも計算するということだろう。もしグループ数が多い場合はforループが必要になり,より長いコードを書く必要があり面倒に感じることになる。

こういう場合のために,DataFrameには簡単にグループ計算を可能にする方法が用意されている。それが.groupby()というメソッドである。以下では.groupby()の使い方をdfを使って3つのステップに分けて説明する。

ステップ1:グループ化する列を指定#

最初のステップでは,グループ化したい列名を引数として.groupby()を実行する。dfcontinentでグループ化したいので次のコードとなる。

df.groupby('continent')

<注意>
このコードはDataFrameを返すわけではない。返すのはグループ計算用のオブジェクトであり,それを使ってグループ計算をおこなう事になる。

実際にコードを実行し,変数df_groupbyに割り当てよう。

df_groupby = df.groupby('continent')

df_groupbyを実行してみよう。

df_groupby
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x124b10230>

何も返されない。表示されているのは,PCメモリーのある箇所にDataFrameGroupByというオブジェクトが存在すると伝えているだけである。

ステップ2:グループ計算したい列を指定#

次にグループ計算したい列を指定するが,次の様な書き方となる。

df_groupby[<グループ計算したい列ラベル>]

ここで[]を使っていることに注意しよう。例としてgdpをグループ計算するとしよう。

df_groupby['gdp']
<pandas.core.groupby.generic.SeriesGroupBy object at 0x124b10800>

ここでも新たなオブジェクトSeriesGroupByのメモリーアドレスが表示されるだけである。どの様に計算するかを指定することにより,計算結果が返されることになる。それが次のステップである。

ステップ3:計算方法の指定#

ステップ1と2がグループ計算の準備段階であり,あとは実際にどのように計算したいかを指定する。例として,平均を考えてみよう。上でも出てきたが,平均はメソッド.mean()を使う。

df_groupby['gdp'].mean()
continent
Asia      6337.666667
Europe    3170.250000
Name: gdp, dtype: float64

グループ計算結果はSeriesとして返されている。.mean()以外にも使える関数は準備されいる。py4macro.see()を使って属性・メソッドを調べてみよう。

py4macro.see(df_groupby['gdp'])
.China              .France             .Germany            .India
.Italy              .Japan              .Korea              .Singapore
.Taiwan             .UK                 .agg                .aggregate
.all                .any                .apply              .bfill
.corr               .count              .cov                .cumcount
.cummax             .cummin             .cumprod            .cumsum
.describe           .diff               .dtype              .ewm
.expanding          .ffill              .fillna             .filter
.first              .get_group          .groups             .head
.hist               .idxmax             .idxmin             .indices
.is_monotonic_decreasing  .is_monotonic_increasing  .last               .max
.mean               .median             .min                .ndim
.ngroup             .ngroups            .nlargest           .nsmallest
.nth                .nunique            .ohlc               .pct_change
.pipe               .plot               .prod               .quantile
.rank               .resample           .rolling            .sample
.sem                .shift              .size               .skew
.std                .sum                .tail               .take
.transform          .unique             .value_counts       .var

主なメソッドとして次を挙げることができる(これらの計算で欠損値は無視される)。

  • mean():平均

  • median():中央値

  • max():最大値

  • min():最小値

  • std():標準偏差

  • var():分散

  • sum():合計

  • cumsum():累積和

  • first():最初の値

  • last():最後の値

  • count():要素数

  • などなど

このリストにない計算をしたい場合もあるだろう。その場合は,上のリストにもある.agg()aggregate()も同じ)を使い,自作の関数を指定することができる。例えば,平均を計算する自作の関数my_mean()を考えてみよう。

def my_mean(x):
    
    return sum(x) / len(x)

ここでのxはステップ2で指定する計算する対象の列と考えれば良いだろう。実際に実行してみよう。

df_groupby['gdp'].agg(my_mean)
continent
Asia      6337.666667
Europe    3170.250000
Name: gdp, dtype: float64

Warning

この場合,()がなく,関数名だけを.agg()の引数にしている。関数名だけを.aggに渡し,.aggが渡された関数を実行するというイメージである。()を付けると.agg()に渡す前に関数を実行することになりエラーとなってしまう。

次のステップ#

1行で書く#

上の説明では3つのステップに分けたが,もちろん次のように3つのステップを次のように1行で書いても構わないし,むしろその場合が多いだろう。

df.groupby('continent')['gdp'].mean()
continent
Asia      6337.666667
Europe    3170.250000
Name: gdp, dtype: float64

このコードは次の様に読むことができる。

dfidでグループ分けして,gdpの平均を計算する。

複数選択#

各ステップでは列や関数を一つだけ選択・設定しているが,それぞれ複数選択することも可能である。

  • ステップ1ではグループ化する上で基準となる列を複数選択

  • ステップ2では計算対象となる列を複数選択

  • ステップ3では.agg()の引数に複数の関数を指定

という具合である。ただその場合は,リストとして列や関数名を指定する必要がある。例えば,ステップ2でgdpinvを選ぶとしよう。

df.groupby('continent')[['gdp','inv']].mean()
gdp inv
continent
Asia 6337.666667 3022.000000
Europe 3170.250000 1771.666667

結果はDataFrameとして返されている。ステップ1もしくは3で複数選択するとDataFrameMultiIndex(階層的な行と列)として返されることになるが,その簡単な説明についてはGapminderを参照して欲しい。より詳しい説明は他のサイトに譲ることにする。

dfのような小さなDataFrameでは.groupbyの威力はあまりピンとこないかも知れない。しかし大きなDataFrameを使うとその恩恵を強く感じることだろう。Gapminderではマクロ経済データを使い.groupby()の使い方の例を示している。興味がある人は是非!