kakakakakku blog

Weekly Tech Blog : Keep on Learning 👍

Numpy の dot() 関数は引数によって「ドット積(内積)」や「行列積」の計算になる

Numpy で dot() 関数を使うと配列同士の「ドット積(内積)」を計算できる.詳しくはドキュメントに載っているけど,dot() 関数は引数 a と b に指定する値(1次元配列/2次元配列)によって挙動が異なる.個人的にわかりにくかったため,具体的に実装しながら整理することにした.またドキュメントには dot() 関数以外を使うべきシナリオもあり代替案も載っている.

numpy.org

1. ドット積(内積)🔡

まず,a と b のどちらにも「1次元配列(ベクトル)」を指定すると,ベクトルの「ドット積(内積)」を計算できる.ドキュメントには以下のように載っている.

If both a and b are 1-D arrays, it is inner product of vectors (without complex conjugation).

なお,そもそも「ドット積(内積)」とはザッと表現するなら以下のような計算式になり,各ベクトルの要素を掛け算した総和となる.詳しくは Cognicull などの参考サイトを見てもらえればと!


  \sum\limits_{i=1}^n a_i b_i \

さっそく dot() 関数を試す.以下のように「1次元配列」と「1次元配列」を指定すると,期待通りに「ドット積(内積)」を計算できる.それぞれ (1x3) + (2x4) = 11 と (1x4) + (2x5) + (3x6) = 32 となる.

a = np.array([1, 2])
b = np.array([3, 4])
np.dot(a, b)
# 11

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.dot(a, b)
# 32

f:id:kakku22:20210531092812p:plain

なお,以下のように要素数(ベクトルの次元数)が異なるとエラーになる.

a = np.array([1, 2, 3])
b = np.array([4, 5, 6, 7])
np.dot(a, b)
# ValueError: shapes (3,) and (4,) not aligned: 3 (dim 0) != 4 (dim 0)

f:id:kakku22:20210531092837p:plain

2. 行列積 🔡

次に a と b のどちらにも「2次元配列(行列)」を指定すると「ドット積(内積)」ではなく「行列積」を計算できる.ドキュメントには以下のように載っている.そして「行列積」を計算するなら dot() 関数よりも matmul() 関数や Numpy の @ 演算子を使うべし!とも載っている.

If both a and b are 2-D arrays, it is matrix multiplication, but using matmul or a @ b is preferred.

「行列積」とはザッと表現するなら以下のような計算式になり,行列同士を掛け算した結果となる.詳しくは Cognicull などの参考サイトに載っている「行列積」の図解を見てもらえればと!


  \begin{pmatrix}
    a & b \\
    c & d
  \end{pmatrix}
  \begin{pmatrix}
    e & f \\
    g & h
  \end{pmatrix}
  =
  \begin{pmatrix}
    ae + bg & af + bh \\
    ce + dg & cf + dh
  \end{pmatrix}

さっそく dot() 関数と matmul() 関数と @ 演算子 を試す.以下のように「2次元配列」と「2次元配列」を指定すると,期待通りに「行列積」を計算できる.以下には (2, 2) x (2, 2) と (3, 3) x (3, 3) の例を載せた.また列数(横)と行数(縦)が一致していれば「行列積」を計算できるため (3, 2) x (2, 2) の例も載せた.そして mutmul() 関数や @ 演算子を使っても同じ結果になっている.

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
np.dot(a, b)
# array([[19, 22],
#        [43, 50]])

np.matmul(a, b)
# array([[19, 22],
#        [43, 50]])

a@b
# array([[19, 22],
#        [43, 50]])

a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = np.array([[10, 11, 12], [13, 14, 15], [16, 17, 18]])
np.dot(a, b)
# array([[ 84,  90,  96],
#        [201, 216, 231],
#        [318, 342, 366]])

np.matmul(a, b)
# array([[ 84,  90,  96],
#        [201, 216, 231],
#        [318, 342, 366]])

a@b
# array([[ 84,  90,  96],
#        [201, 216, 231],
#        [318, 342, 366]])

a = np.array([[1, 2], [3, 4], [5, 6]])
b = np.array([[7, 8], [9, 10]])
np.dot(a, b)
# array([[ 25,  28],
#        [ 57,  64],
#        [ 89, 100]])

np.matmul(a, b)
# array([[ 25,  28],
#        [ 57,  64],
#        [ 89, 100]])

a@b
# array([[ 25,  28],
#        [ 57,  64],
#        [ 89, 100]])

f:id:kakku22:20210531092950p:plain

なお,以下の (2, 3) x (2, 2) のように,列数(横)と行数(縦)が一致していない場合はエラーになる.

a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([[7, 8], [9, 10]])
np.dot(a, b)
# ValueError: shapes (2,3) and (2,2) not aligned: 3 (dim 1) != 2 (dim 0)

np.matmul(a, b)
# ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 3)

a@b
# ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 3)

f:id:kakku22:20210531093115p:plain

まとめ 🔡

Numpy の dot() 関数の挙動を整理するために試した.引数 a と b に指定する値(1次元配列/2次元配列)によって「ドット積(内積)」や「行列積」の計算になることを確認できた(正確には他の計算パターンもある).またドキュメントには dot() 関数以外を使うべきシナリオもあり代替案も載っているため,特に「行列積」の場合は matmul() 関数や Numpy の @ 演算子を使うことを覚えておこう!

numpy.org

関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

Pandas で時系列データをグループ化して集計できる「Grouper」

Pandas で groupby() 関数を使うと,データセットをグループ化して集計できる.さらに Grouper オブジェクトと組み合わせると,より高機能なグループ化を実現できる.今回は groupby() 関数と Grouper オブジェクトを組み合わせて「時系列データの集計」を試す.最後に関連する resample() 関数も試す.

データセット 🪢

今回使うサンプルデータセットを準備する.まず,Pandas の date_range() 関数を使って 2020/1/1 ~ 2020/12/31 の範囲で1年間の DatetimeIndex を作る.そして DatetimeIndex をインデックス値とした DataFrame を作る.今回は count カラムとして 1 ~ 99 の範囲で乱数を含めておく.乱数は Numpy の random.randint() 関数を使う.結果的に以下のような DataFrame になる.

dates = pd.date_range(start='2020-01-01', end='2020-12-31')
dates
# DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
#                '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08',
#                '2020-01-09', '2020-01-10',
#                ...
#                '2020-12-22', '2020-12-23', '2020-12-24', '2020-12-25',
#                '2020-12-26', '2020-12-27', '2020-12-28', '2020-12-29',
#                '2020-12-30', '2020-12-31'],
#               dtype='datetime64[ns]', length=366, freq='D')

df = pd.DataFrame(np.random.randint(1, 100, 366), index=dates, columns=['count'])
df
# 2020-01-01   13
# 2020-01-02   50
# 2020-01-03   44
# 2020-01-04   12
# 2020-01-05   25
# 2020-01-06   82
# 2020-01-07   4
# 2020-01-08   2
# 2020-01-09   27
# 2020-01-10   38
# (中略)

f:id:kakku22:20210511124750p:plain

groupby() 関数と Grouper オブジェクトを組み合わせる 🪢

以下のように DataFrame に対して groupby() 関数と Grouper オブジェクトを組み合わせる.まず pd.Grouper(freq='M') と pd.Grouper(freq='Q') を試す.M は「月末集計」で Q は「四半期集計」となる.そして今回は mean() 関数を使って「平均値」を取得する.このように Grouper オブジェクト を使うと簡単に「時系列データの集計」を実現できる.

  • M : month end frequency
  • Q : quarter end frequency
# month end frequency
df.groupby(pd.Grouper(freq='M')).mean()
# 2020-01-31   40.096774
# 2020-02-29   47.655172
# 2020-03-31   46.064516
# 2020-04-30   54.766667
# 2020-05-31   54.354839
# 2020-06-30   49.100000
# 2020-07-31   57.000000
# 2020-08-31   46.387097
# 2020-09-30   41.333333
# 2020-10-31   36.967742
# 2020-11-30   40.266667
# 2020-12-31   57.645161

# quarter end frequency
df.groupby(pd.Grouper(freq='Q')).mean()
# 2020-03-31   44.538462
# 2020-06-30   52.758242
# 2020-09-30   48.315217
# 2020-12-31   45.010870

f:id:kakku22:20210511125429p:plain

また以下のドキュメント(DateOffset objects → Offset aliases と Anchored offsets)を読むと freq に指定できる識別子は他にも多くある.代表的な例を以下に抜粋した.

  • W : weekly frequency
  • SM : semi-month end frequency (15th and end of month)
  • H : hourly frequency
  • W-SUN : weekly frequency (Sundays). Same as ‘W’
  • W-MON : weekly frequency (Mondays)
  • W-TUE : weekly frequency (Tuesdays)
  • etc

pandas.pydata.org

例えば pd.Grouper(freq='W-MON') を指定すると「週次集計」で軸にする「曜日(ここでは月曜日)」を指定できる.デフォルトでは W は W-SUN となる.正確に値を計算すると 2020/1/13 (月) の 32.428571 は 2020/1/7 (火) ~ 2020/1/13 (月) の期間での平均値となるため,計算式としては (4 + 2 + 27 + 38 + 23 + 62 + 71) / 7 = 32.428571 として確認できる.

# weekly frequency (Mondays)
df.groupby(pd.Grouper(freq='W-MON')).mean()
# 2020-01-06   37.666667
# 2020-01-13   32.428571
# 2020-01-20   47.142857
# 2020-01-27   35.714286
# 2020-02-03   37.571429
# 2020-02-10   66.000000
# 2020-02-17   43.285714
# 2020-02-24   48.000000
# (中略)

f:id:kakku22:20210511130035p:plain

Grouper オブジェクトの key パラメータ 🪢

デフォルトの挙動では DataFrame のインデックス値が DatetimeIndex や TimedeltaIndex である必要があるため,それ以外の DataFrame だと TypeError: Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, but got an instance of 'RangeIndex' というエラーが出る.その場合は Grouper オブジェクトの key パラメータを使ってカラムを指定する.

動作確認をするために以下のようなサンプルデータセットを準備する.DataFrame のインデックス値は 0,1,2 ... という数値とし,date カラムに日付を含める.そして pd.Grouper(key='date', freq='M') のように key パラメータを指定すると,期待した平均値となった.

df = pd.DataFrame(
    [
        {'date': pd.Timestamp('2020-01-01'), 'counter': 1},
        {'date': pd.Timestamp('2020-01-02'), 'counter': 2},
        {'date': pd.Timestamp('2020-01-03'), 'counter': 3},
        {'date': pd.Timestamp('2020-02-01'), 'counter': 4},
        {'date': pd.Timestamp('2020-02-02'), 'counter': 5},
        {'date': pd.Timestamp('2020-02-03'), 'counter': 6}
    ]
)
df
# 0    2020-01-01  1
# 1    2020-01-02  2
# 2    2020-01-03  3
# 3    2020-02-01  4
# 4    2020-02-02  5
# 5    2020-02-03  6

df.groupby(pd.Grouper(key='date', freq='M')).mean()
# 2020-01-31   2
# 2020-02-29   5

f:id:kakku22:20210511132345p:plain

関連する resample() 関数も試す 🪢

Pandas のドキュメントを読むと,resample() 関数を使った「時系列データの集計」の例も載っていた.最近読んだ「Pandas ライブラリ活用入門」にも resample() 関数の例が載っていた.

実際に試したところ,Grouper オブジェクトと同じような集計ができた.例えば,以下は同じデータセットに対して「月末集計」をした結果で,平均値は完全に一致していた.また on パラメータを使ってカラムを指定することもできる.今回試した範囲だと Grouper オブジェクトと resample() 関数の機能面での差は確認できなかった.どう使い分けるんだろう?

df.resample('M').mean()
# 2020-01-31   40.096774
# 2020-02-29   47.655172
# 2020-03-31   46.064516
# 2020-04-30   54.766667
# 2020-05-31   54.354839
# 2020-06-30   49.100000
# 2020-07-31   57.000000
# 2020-08-31   46.387097
# 2020-09-30   41.333333
# 2020-10-31   36.967742
# 2020-11-30   40.266667
# 2020-12-31   57.645161

df.resample('Q').mean()
df.resample('W-MON').mean()
df.resample(on='date', rule='M').mean()

f:id:kakku22:20210511132840p:plain

まとめ 🪢

今回は Pandas で groupby() 関数と Grouper オブジェクトを組み合わせて「時系列データの集計」を試した.「月末集計」や「四半期集計」そして「週集計」など高機能なグループ化を実現できる.引き続き気になった機能はどんどん試していくぞー👏

関連記事

kakakakakku.hatenablog.com

NumPy で「単位行列」を生成できる identity() 関数と eye() 関数の違い

NumPy で「単位行列」を生成するときに identity() 関数と eye() 関数がサポートされている.それぞれの違いを整理するためにドキュメントを読みながら試してみた.今回は Numpy 1.20.2 を前提にする.ドキュメントには (almost) equivalent function と書いてあってほぼ同じ機能だけど,簡単に言えば identity() 関数はとてもシンプルで eye() 関数では一部パラメータを指定できる自由度がある.

なお「単位行列」とは,以下のように斜めの値(対角成分)が全て 1 の行列のことで,どんな行列との内積も行列の値が変化しないため,数字の 1 のような性質を持った行列と言える.行列は「はてな記法一覧」の TeX 記法 [tex:] を使っている.


  \left(
    \begin{array}{ccc}
      1 & 0 & 0 \\
      0 & 1 & 0 \\
      0 & 0 & 1
    \end{array}
  \right)

identity 関数を試す 🔢

まず,identity() 関数を試す.identity() 関数はシンプルで,引数に指定したサイズの「正方行列」で「単位行列」を生成する.サンプルとして identity(3) と identity(5) を載せておく.

import numpy as np

np.identity(3)
# array([[1., 0., 0.],
#        [0., 1., 0.],
#        [0., 0., 1.]])

np.identity(5)
# array([[1., 0., 0., 0., 0.],
#        [0., 1., 0., 0., 0.],
#        [0., 0., 1., 0., 0.],
#        [0., 0., 0., 1., 0.],
#        [0., 0., 0., 0., 1.]])

f:id:kakku22:20210510150823p:plain

配列の中に含まれている数値はデフォルトでは float64 型になる.identity() 関数に dtype パラメータを指定すると型を変えられるため,以下は int を指定した.実際には int64 型になった.

np.identity(3).dtype
# dtype('float64')

np.identity(5, dtype=int)
# array([[1, 0, 0, 0, 0],
#        [0, 1, 0, 0, 0],
#        [0, 0, 1, 0, 0],
#        [0, 0, 0, 1, 0],
#        [0, 0, 0, 0, 1]])

np.identity(5, dtype=int).dtype
# dtype('int64')

f:id:kakku22:20210510151151p:plain

eye() 関数を試す 🔢

次に eye() 関数を試す.基本的には identity() 関数と同じで,引数に指定したサイズの「正方行列」で「単位行列」を生成する.サンプルとして eye(3) と eye(5) を載せておく.

np.eye(3)
# array([[1., 0., 0.],
#        [0., 1., 0.],
#        [0., 0., 1.]])

np.eye(5)
# array([[1., 0., 0., 0., 0.],
#        [0., 1., 0., 0., 0.],
#        [0., 0., 1., 0., 0.],
#        [0., 0., 0., 1., 0.],
#        [0., 0., 0., 0., 1.]])

f:id:kakku22:20210510151543p:plain

配列の中に含まれている数値もデフォルトでは float64 型で同じ.eye() 関数の dtype パラメータに int を指定すると,同じく int64 型になる.ここまでは特に違いはなかった.

np.eye(3).dtype
# dtype('float64')

np.eye(3, dtype=int)
# array([[1, 0, 0],
#        [0, 1, 0],
#        [0, 0, 1]])

np.eye(3, dtype=int).dtype
# dtype('int64')

f:id:kakku22:20210510151647p:plain

identity() 関数との違いとして,eye() 関数では k プロパティがサポートされている.k プロパティを指定すると,以下のように値が 1 になる軸を上下にズラすことができる.以下はサンプルとして k=1 と k=-1 の例を載せている.実務上だと k プロパティをどういう場面で使うんだろう?

他にも eye() 関数には M プロパティ(列数を指定して正方行列ではない単位行列を生成する)など,いくつかのプロパティがある.

np.eye(5, k=1)
# array([[0., 1., 0., 0., 0.],
#        [0., 0., 1., 0., 0.],
#        [0., 0., 0., 1., 0.],
#        [0., 0., 0., 0., 1.],
#        [0., 0., 0., 0., 0.]])

np.eye(5, k=-1)
# array([[0., 0., 0., 0., 0.],
#        [1., 0., 0., 0., 0.],
#        [0., 1., 0., 0., 0.],
#        [0., 0., 1., 0., 0.],
#        [0., 0., 0., 1., 0.]])

f:id:kakku22:20210510151956p:plain

まとめ 🔢

最近は NumPy を学んでいる.今回は NumPyで「単位行列」を生成できる identity() 関数と eye() 関数を試しつつ,機能の違いも確認した.ドキュメントには (almost) equivalent function と書いてあるけど,eye() 関数の方が指定できるパラメータが多くある.

関連記事 🔢

kakakakakku.hatenablog.com

NumPy で「n次元配列」を「1次元配列」に変換できる ravel() 関数と flatten() 関数の違い

NumPy で「n次元配列」を「1次元配列」に変換するときに ravel() 関数と flatten() 関数がサポートされている.ravel() 関数に関しては,正確には numpy.ravel() 関数と numpy.ndarray.ravel() 関数がある.それぞれの違いを整理するためにドキュメントを読みながら試してみた.今回は Numpy 1.20.2 を前提にする.

簡単に違いを整理しておくと,ravel() 関数は基本的には「参照」を返して,flatten() 関数は「コピー」を返す.パフォーマンスにも差がある.個人的には flatten という単語は Ruby で見たことがあるけど,ravel という単語は見たことがなかった!調べてみたら「ほぐす」や「ほどく」という意味だった.なるほどー💡

ravel() 関数を試す 🔢

まず,ravel() 関数(正確には numpy.ndarray.ravel() 関数)を試す.以下のように 3x3 の配列 base_array を作り,ravel() 関数を使うと,配列 ravel_array の結果の通り「1次元配列」に変換できた.

import numpy as np

base_array = np.array(
    [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ]
)
base_array
# array([[1, 2, 3],
#        [4, 5, 6],
#        [7, 8, 9]])

ravel_array = base_array.ravel()
ravel_array
# array([1, 2, 3, 4, 5, 6, 7, 8, 9])

f:id:kakku22:20210510140705p:plain

次に配列 base_array の値を更新する.すると,配列 ravel_array の値も更新されていた.ravel() 関数は基本的には「参照」を返すため,配列 base_array だけではなく,配列 ravel_array にも影響する.なお,あえて「基本的には」と書いたのはドキュメントに以下のように書いてあるため.

A 1-D array, containing the elements of the input, is returned. A copy is made only if needed.

numpy.ravel — NumPy v1.20 Manual

base_array[1][1] = 50

base_array
# array([[ 1,  2,  3],
#        [ 4, 50,  6],
#        [ 7,  8,  9]])

ravel_array
# array([ 1,  2,  3,  4, 50,  6,  7,  8,  9])

f:id:kakku22:20210510121955p:plain

flatten() 関数を試す 🔢

次に flatten() 関数(正確には numpy.ndarray.flatten() 関数)を試す.同じく 3x3 の配列 base_array を作り,flatten() 関数を使うと,配列 flatten_array の結果の通り「1次元配列」に変換できた.ここまでは ravel() 関数と同じ挙動になる.

base_array = np.array(
    [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ]
)
base_array
# array([[1, 2, 3],
#        [4, 5, 6],
#        [7, 8, 9]])

flatten_array = base_array.flatten()
flatten_array
# array([1, 2, 3, 4, 5, 6, 7, 8, 9])

f:id:kakku22:20210510123214p:plain

同じく配列 base_array の値を更新する.すると,配列 flatten_array の値は更新されていなかった.flatten() 関数は「コピー」を返すため,配列 flatten_array には影響しなかった.ドキュメントに以下のように書いてある.

Return a copy of the array collapsed into one dimension.

numpy.ndarray.flatten — NumPy v1.20 Manual

base_array[1][1] = 50

base_array
# array([[ 1,  2,  3],
#        [ 4, 50,  6],
#        [ 7,  8,  9]])

flatten_array
# array([1, 2, 3, 4, 5, 6, 7, 8, 9])

f:id:kakku22:20210510123438p:plain

ravel() 関数と flatten() 関数の実行時間を比較する 🔢

最後は Jupyter Notebook の %%timeit マジックコマンドを使って,ravel() 関数と flatten() 関数の実行時間を比較する.実行環境によって結果に差が出るため,あくまで MacBook Pro で計測したサンプル値として確認してもらえればと🙏

結果としては以下の通り,ある程度差が出る.flatten() 関数は相対的に遅く,「コピー」を返すためにメモリも多く消費するため,アプリケーション目線で用途によって使い分けるのが良さそう.

%%timeit
base_array.ravel()
# 152 ns ± 0.719 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%%timeit
base_array.flatten()
# 560 ns ± 7.72 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

f:id:kakku22:20210510125415p:plain

まとめ 🔢

Pandas に続き,今度は NumPy の機能を学んでいる.今回は NumPy で「n次元配列」を「1次元配列」に変換できる ravel() 関数と flatten() 関数を試しつつ,機能の違いも確認した.

関連記事 🔢

kakakakakku.hatenablog.com

Pandas の機能を実践的に学ぶならこの1冊!「Pandas ライブラリ活用入門」を読んだ

個人的に Pandas を使ってデータ分析をする機会が増えてきて,今までの浅い経験ではうまく使いこなせず,Pandas を中心に細かく学び直している.最近 Pandas 関連の記事を多く書いていることにも関連しているし,少し前には Pandas を学べる「Kaggle Courses」を紹介した.そして並行して「Pandas ライブラリ活用入門」を読んだため,書評をまとめておく.

目次 📚

全部で「第18章」から構成されている.また Python 入門者のために充実した付録も載っている.本書の序盤にも「読み方」が載っているけど,個人的には Pandas を優先的に学ぶなら「第1,2,3部」を読んで,機械学習にも興味があれば「第4部」まで読むと良いと思う.とは言え,Pandas を中心に主要な機能を解説したリファレンス的な内容になるため,全体をザッと流し読みしてから,気になった機能を実際に試しながら読み直すスタイルが僕には1番合っていた👍

  • 第1部「基本的な使い方の基本」
    • 第1ç«  : DataFrame の基礎
    • 第2ç«  : pandas のデータ構造
    • 第3ç«  : プロットによるグラフ描画
  • 第2部「データ操作によるクリーニング」
    • 第4ç«  : データを組み立てる
    • 第5ç«  : 欠損データへの対応
    • 第6ç«  : "整然データ"を作る
  • 第3部「データの準備―変換/整形/結合など」
    • 第7ç«  : データ型の概要と変換
    • 第8ç«  : テキスト文字列の操作
    • 第9ç«  : apply による関数の適用
    • 第10ç«  : groupby 演算による分割-適用-結合
    • 第11ç«  : 日付/時刻データの操作
  • 第4部「モデルをデータに適合させる」
    • 第12ç«  : 線形モデル
    • 第13ç«  : 一般化線形モデル
    • 第14ç«  : モデルを診断する
    • 第15ç«  : 正則化で過学習に対処する
    • 第16ç«  : クラスタリング
  • 第5部「締めくくり―次のステップへ」
    • 第17ç«  : pandas 周辺の強力な機能
    • 第18ç«  : さらなる学びのための情報源
  • 第6部「付録」
    • 付録 A : インストール
    • 付録 B : コマンドライン
    • 付録 C : プロジェクトのテンプレート
    • 付録 D : Python の使い方
    • 付録 E : ワーキングディレクトリ
    • 付録 F : 環境
    • 付録 G : パッケージのインストール
    • 付録 H : ライブラリのインポート
    • 付録 I : リスト
    • 付録 J : タプル
    • 付録 K : 辞書
    • 付録 L : 値のスライス
    • 付録 M : ループ
    • 付録 N : 内包表記 (comprehension)
    • 付録 O : 関数
    • 付録 P : 範囲とジェネレータ
    • 付録 Q : 複数代入
    • 付録 R : numpy の ndarray
    • 付録 S : クラス
    • 付録 T : Odo (TheShapeshifter)

本書の誤植は以下に載っている.

book.impress.co.jp

本書で使う Jupyter Notebook とデータセットは以下の GitHub リポジトリに公開されている.

github.com

Pandas 基礎の基礎 : DataFrame と Series 📚

第1章「DataFrame の基礎」と第2章「pandas のデータ構造」を読めば,Pandas の代表的なオブジェクトである DataFrame と Series の基本的な操作(関数や属性など)を学べる.例えばよく使う関数だと pd.read_csv() / head() / tail() / loc() / iloc() や,プロパティだと shape / columns など.他にもいろいろ載っている.

import pandas as pd
df = pd.read_csv('../data/gapminder.tsv', sep='\t')
type(df)
# pandas.core.frame.DataFrame
df.shape
# (1704, 6)
df.columns
# Index(['country', 'continent', 'year', 'lifeExp', 'pop', 'gdpPercap'], dtype='object')

f:id:kakku22:20210506100321p:plain

可視化 📚

第3章「プロットによるグラフ描画」では可視化テクニックを学べる.読む前は Matplotlib の解説かなぁーと予想していたけど,実際には Matplotlib だけではなく Seaborn や Pandas の DataFrame.plot を使った例も紹介されていた.Seaborn を使えばより高度なグラフをお手軽に描けるし,シンプルなグラフで良ければ Pandas でも描ける.また本書の冒頭にはグラフの例がカラー印刷で載っているため,グラフを見るだけでも楽しめる📊

  • ヒストグラム
  • 密度プロット
  • 散布図
  • hexbin(六角形ビニング)プロット
  • 箱ひげ図
  • etc
import matplotlib.pyplot as plt
import seaborn as sns
tips = sns.load_dataset('tips')
sns.lmplot(x='total_bill', y='tip', data=tips)
plt.show()

f:id:kakku22:20210506100430p:plain

実際に自分で考えたグラフを描こうとすると用語も理解しておく必要があり,Matplotlib のドキュメントに載っている Anatomy of a figure がとてもわかりやすかった.以下に引用して載せておく.

f:id:kakku22:20210506100640p:plain
Anatomy of a figure — Matplotlib documentation より引用

そして Seaborn を使った「ペアプロット図(散布図行列)」の実装は以下の記事で試したりもした

kakakakakku.hatenablog.com

整然データ 📚

第6章「"整然データ"を作る」を中心に第4,5,6章では「整然データ (Tidy Data)」を実現する Tips を学んでいく.「整然データ」は簡単に言えば,データセットを解析しやすくするために構造化したフレームワークで,以下の記事がとてもわかりやすかった.

id.fnshr.info

具体的な Tips としては,具体的には pd.concat() や merge() 関数を使ったデータセットの連結やインデックスの再設定,そして melt() 関数を使って「横持ち」のデータセットを「縦持ち」に変換するなど,実践的で参考になった.

また「欠損値 (Missing data) : NaN」の取り扱いも出てくる.NaN に関しては以下の記事で詳しく試した.

kakakakakku.hatenablog.com

日付操作 📚

第11章「日付/時刻データの操作」では,Pandas の日付や時刻に関連する機能を学べる.例えば pd.to_datetime() 関数を使うと指定したカラムを object 型から datetime64 型に変換できる.以下の例では Date を date_dt に datetime64 型として変換している.

import pandas as pd
ebola = pd.read_csv('../data/country_timeseries.csv')
ebola['date_dt'] = pd.to_datetime(ebola['Date'])
ebola.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 122 entries, 0 to 121
# Data columns (total 19 columns):
#  #   Column               Non-Null Count  Dtype         
# ---  ------               --------------  -----         
#  0   Date                 122 non-null    object        
#  1   Day                  122 non-null    int64         
#  2   Cases_Guinea         93 non-null     float64       
#  3   Cases_Liberia        83 non-null     float64       
#  4   Cases_SierraLeone    87 non-null     float64       
#  5   Cases_Nigeria        38 non-null     float64       
#  6   Cases_Senegal        25 non-null     float64       
#  7   Cases_UnitedStates   18 non-null     float64       
#  8   Cases_Spain          16 non-null     float64       
#  9   Cases_Mali           12 non-null     float64       
#  10  Deaths_Guinea        92 non-null     float64       
#  11  Deaths_Liberia       81 non-null     float64       
#  12  Deaths_SierraLeone   87 non-null     float64       
#  13  Deaths_Nigeria       38 non-null     float64       
#  14  Deaths_Senegal       22 non-null     float64       
#  15  Deaths_UnitedStates  18 non-null     float64       
#  16  Deaths_Spain         16 non-null     float64       
#  17  Deaths_Mali          12 non-null     float64       
#  18  date_dt              122 non-null    datetime64[ns]
# dtypes: datetime64[ns](1), float64(16), int64(1), object(1)
# memory usage: 18.2+ KB

f:id:kakku22:20210506100833p:plain

また pd.read_csv() 関数に parse_dates パラメータを指定すると,データセットを読み込むときに datetime64 型に変換できる.

import pandas as pd
ebola = pd.read_csv('../data/country_timeseries.csv', parse_dates=[0])
ebola.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 122 entries, 0 to 121
# Data columns (total 18 columns):
#  #   Column               Non-Null Count  Dtype         
# ---  ------               --------------  -----         
#  0   Date                 122 non-null    datetime64[ns]
#  1   Day                  122 non-null    int64         
#  2   Cases_Guinea         93 non-null     float64       
#  3   Cases_Liberia        83 non-null     float64       
#  4   Cases_SierraLeone    87 non-null     float64       
#  5   Cases_Nigeria        38 non-null     float64       
#  6   Cases_Senegal        25 non-null     float64       
#  7   Cases_UnitedStates   18 non-null     float64       
#  8   Cases_Spain          16 non-null     float64       
#  9   Cases_Mali           12 non-null     float64       
#  10  Deaths_Guinea        92 non-null     float64       
#  11  Deaths_Liberia       81 non-null     float64       
#  12  Deaths_SierraLeone   87 non-null     float64       
#  13  Deaths_Nigeria       38 non-null     float64       
#  14  Deaths_Senegal       22 non-null     float64       
#  15  Deaths_UnitedStates  18 non-null     float64       
#  16  Deaths_Spain         16 non-null     float64       
#  17  Deaths_Mali          12 non-null     float64       
# dtypes: datetime64[ns](1), float64(16), int64(1)
# memory usage: 17.3 KB

f:id:kakku22:20210506100846p:plain

さらに後半では「リサンプリング」と「タイムゾーン変換」の例も載っている.Pandas では resample() 関数を使って「ダウンサンプリング(高い周期から低い周期)」や「アップサンプリング(低い周期や高い周期)」に変換できる.tz_convert() 関数を使うとタイムゾーンを変換できる.

機械学習 📚

Pandas だけではなく機械学習にも興味があれば,第12章「線形モデル」から第16章「クラスタリング」までを読むと,Python の statsmodels や scikit-learn を使った機械学習モデルの実装例を学べる.目次の一部を抜粋すると以下などが載っている.ただし統計学の基礎から学べるわけではなく,あくまでライブラリを使った実装例の紹介が中心となるため,機械学習関連の他の本を併読するべきだと思う.前提知識に依存するところではあるけど,個人的には知識不足もあり本書だけだと理解できないところもあったし,本書の読者層も似た傾向になる気がする.

  • 線形モデル
    • 線形回帰
    • 重回帰
  • 一般化線形モデル
    • ロジスティック回帰
    • ポアソン回帰
  • 正規化
    • リッジ回帰
    • ラッソ回帰
    • 交差検証

まとめ 📚

Pandas の機能を深く理解するために「Pandas ライブラリ活用入門」を読んだ.実際に試して気付くことも多く,全体をザッと流し読みしてから,気になった機能を試しながら読み直した.リファレンス的な内容ではあるけど,Pandas でできることを全体把握できたため,読んで良かった💡今後は Pandas を使った機械学習など,実践的な内容に学習項目をステップアップしていくぞー!