背景
- 事あるごとにGoogle検索で対応しているが、一時的な理解で血となり肉となっていない。
- 断片的な知識だけでは、応用力に乏しくいざという時に対応できない。
- 衰えた記憶力に打ち勝つには、手を動かし理解しそれを説明する事で脳に刻みたい。
- 先達の人たちからの金言「公式マニュアルは一読すべし」を実行したい。
何をどのように
-
Pandas公式ページで
User Guide
の最後にあるCookBook
を対象とする。 - 公式ページには、具体的な事例とstackoverlow.comなどに投稿され、
活発に議論された質疑のリンクが掲載されています。 - なんでそうなるの?疑問を解消しながら、実際手を動かしてゴールに至るまでを書いています
この投稿の構成について
- クックブックの構成に従って見出しを構成し、掲載されているコードを引用します。
(一部数値やコードを修正しています) - また
stackoverflow.com
の投稿も内容によって掲載しています。(掲載基準は個人判断です) - 見出しの末尾にある
はstackoverflow.comから事例です。
- 投稿のタイトルに関しては、最初は機械翻訳で違和感あれば拙い読解力で修正しています。
- 見出しの末尾に付けた
は、これは便利!,クールだ!,**いけてる!**という個人的見解です。
\small{\mathsf{ Date:Feb \ 05, 2020 \ Version: 1.0.1 }}
Pandas 公式サイト クックブック
Idioms(慣用句)
If-then・・・
1つの列でif-then / if-then-elseを使用し、別の1つ以上の列に割り当てます。
In [1]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
'BBB': [10, 20, 30, 40],
'CCC': [100, 50, -30, -50]})
In [2]: df
Out[2]:
AAA BBB CCC
0 4 10 100
1 5 20 50
2 6 30 -30
3 7 40 -50
1列のif-then
df.loc[df.AAA >= 5, 'BBB'] = -1
2つの列に割り当てられたif-thenの例
df.loc[df.AAA >= 5, ['BBB', 'CCC']] = 555
条件を変えて別の行を更新する
df.loc[df.AAA < 5, ['BBB', 'CCC']] = 2000
マスク値を条件とする
In [9]: df_mask = pd.DataFrame({'AAA': [True] * 4,
'BBB': [False] * 4,
'CCC': [True, False] * 2})
In [10]: df.where(df_mask, -1000)
Out[10]:
AAA BBB CCC
0 4 -1000 2000
1 5 -1000 -1000
2 6 -1000 555
3 7 -1000 -1000
[コード リーディング]
- データフレーム作成で、[True] * 4 は[True,True,True,True]となります。
- mask_dfの内容は以下になります。
AAA | BBB | CCC | |
---|---|---|---|
0 | True | False | True |
1 | True | False | False |
2 | True | False | True |
3 | True | False | False |
- whereメソッドは第一引数がTrue場合、要素が使用され、それ以外は第二引数が使用される。
np.whereを使って、if-then-else代用する
# np.where(condition, true, false)
df['logic'] = np.where(df['AAA'] > 5, 'high', 'low')
AAA | BBB | CCC | logic | |
---|---|---|---|---|
0 | 4 | 2000 | 2000 | low |
1 | 5 | 555 | 555 | low |
2 | 6 | 555 | 555 | high |
3 | 7 | 555 | 555 | high |
Splitting(分轄)
In [16]: df
Out[16]:
AAA BBB CCC
0 4 10 100
1 5 20 50
2 6 30 -30
3 7 40 -50
In [17]: df[df.AAA <= 5]
Out[17]:
AAA BBB CCC
0 4 10 100
1 5 20 50
In [18]: df[df.AAA > 5]
Out[18]:
AAA BBB CCC
2 6 30 -30
3 7 40 -50
ブール値でデータフレームを分割する
id | sex | age | visits | points | |
---|---|---|---|---|---|
0 | 128 | 1 | 36 | 6 | 1980 |
1 | 324 | 0 | 29 | 9 | -50 |
2 | 287 | 0 | 41 | 12 | -239 |
3 | 423 | 1 | 33 | 33 | 1570 |
4 | 589 | 0 | 19 | 10 | 10 |
これをブール値で分割してみます。
[コード リーディング]
# mのブール値を確認します
m = df['sex'] != 0
m
0 True
1 False
2 False
3 True
4 False
Name: sex, dtype: bool
# 条件mで抽出、mの否定で抽出する
female, male = df[m], df[~m]
female
id | sex | age | visits | points | |
---|---|---|---|---|---|
0 | 128 | 1 | 36 | 6 | 1980 |
3 | 423 | 1 | 33 | 33 | 1570 |
male
id | sex | age | visits | points | |
---|---|---|---|---|---|
1 | 324 | 0 | 29 | 9 | -50 |
2 | 287 | 0 | 41 | 12 | -239 |
4 | 589 | 0 | 19 | 10 | 10 |
Building criteria(条件の作成)
id | sex | age | visits | points | |
---|---|---|---|---|---|
0 | 128 | 1 | 36 | 6 | 1980 |
1 | 324 | 0 | 29 | 9 | -50 |
2 | 287 | 0 | 41 | 12 | -239 |
3 | 423 | 1 | 33 | 33 | 1570 |
4 | 589 | 0 | 19 | 10 | 10 |
# (A条件) & (B条件) andではエラーになります
df.loc[(df['BBB'] < 25) & (df['CCC'] >= -40), 'AAA']
0 4
1 5
Name: AAA, dtype: int64
# (A条件) | (B条件) orではエラーになります
df.loc[(df['BBB'] > 25) | (df['CCC'] >= -40), 'AAA']
0 4
1 5
2 6
3 7
Name: AAA, dtype: int64
# &,|を使ってデータの更新ができます
df.loc[(df['BBB'] > 25) | (df['CCC'] >= 75), 'AAA'] = 0.1
AAA | BBB | CCC | |
---|---|---|---|
0 | 0.1 | 10 | 100 |
1 | 5 | 20 | 50 |
2 | 0.1 | 30 | -30 |
3 | 0.1 | 40 | -50 |
argsort関数を使用して特定の値に最も近いデータを持つ行を選択します
df = pd.DataFrame({'AAA': [4, 5, 6, 7],
'BBB': [10, 20, 30, 40],
'CCC': [100, 50, -30, -50]})
In [26]: df
Out[26]:
AAA BBB CCC
0 4 10 100
1 5 20 50
2 6 30 -30
3 7 40 -50
In [27]: aValue = 43.0
In [28]: df.loc[(df.CCC - aValue).abs().argsort()]
Out[28]:
AAA BBB CCC
1 5 20 50
0 4 10 100
2 6 30 -30
3 7 40 -50
[コード リーディング]
# 単純な計算
(df.CCC - aValue)
0 57.0
1 7.0
2 -73.0
3 -93.0
Name: CCC, dtype: float64
# 単純な計算の結果を絶対値にする
(df.CCC - aValue).abs()
0 57.0
1 7.0
2 73.0
3 93.0
Name: CCC, dtype: float64
# 単純な計算の結果を絶対値にして、その数値を並び替えた結果のindexを返します。
(df.CCC - aValue).abs().argsort()
0 1
1 0
2 2
3 3
Name: CCC, dtype: int64
二項演算子を使用して、基準のリストを動的に絞り込みます
In [29]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
'BBB': [10, 20, 30, 40],
'CCC': [100, 50, -30, -50]})
In [30]: df
Out[30]:
AAA BBB CCC
0 4 10 100
1 5 20 50
2 6 30 -30
3 7 40 -50
In [31]: Crit1 = df.AAA <= 5.5
In [32]: Crit2 = df.BBB == 10.0
In [33]: Crit3 = df.CCC > -40.0
# ハードコーディングで結合します
In [34]: AllCrit = Crit1 & Crit2 & Crit3
In [35]: import functools
In [36]: CritList = [Crit1, Crit2, Crit3]
# reduce関数ですべての条件(積算して)を作成します
In [37]: AllCrit = functools.reduce(lambda x, y: x & y, CritList)
In [38]: df[AllCrit]
Out[38]:
AAA BBB CCC
0 4 10 100
[コード リーディング]
# それぞれの条件をデータフレームにして確認します。
tmp = pd.DataFrame({'CritList_0':CritList[0].tolist(),
'CritList_1':CritList[1].tolist(),
'CritList_2':CritList[2].tolist(),
'AllCrit':AllCrit.tolist()
}, index=[0,1,2,3])
- すべての条件を掛け合わせた
AllCrit
でTrue
は、インデックスが0
の行となります
CritList_0 | CritList_1 | CritList_2 | AllCrit | |
---|---|---|---|---|
0 | True | True | True | True |
1 | True | False | True | False |
2 | False | False | True | False |
3 | False | False | False | False |
Selection(選択)
Dataframes(データフレーム)
行ラベルと値条件の両方を使用する
In [39]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
'BBB': [10, 20, 30, 40],
'CCC': [100, 50, -30, -50]})
In [40]: df
Out[40]:
AAA BBB CCC
0 4 10 100
1 5 20 50
2 6 30 -30
3 7 40 -50
In [41]: df[(df.AAA <= 6) & (df.index.isin([0, 2, 4]))]
Out[41]:
AAA BBB CCC
0 4 10 100
2 6 30 -30
ラベルでのスライスではlocを、位置スライスにilocを使用します
AAA | BBB | CCC | |
---|---|---|---|
foo | 4 | 10 | 100 |
bar | 5 | 20 | 50 |
boo | 6 | 30 | -30 |
kar | 7 | 40 | -50 |
In [43]: df.loc['bar':'kar'] # Label
Out[43]:
AAA BBB CCC
bar 5 20 50
boo 6 30 -30
kar 7 40 -50
# Generic
In [44]: df.iloc[0:3]
Out[44]:
AAA BBB CCC
foo 4 10 100
bar 5 20 50
boo 6 30 -30
学んだ重要なポイント
- 位置指向(Pythonスライシングスタイル:終了を除く)
- ラベル指向(非Pythonスライススタイル:終了を含む)
インデックスがゼロ以外の開始または単位増分ではない整数で構成される場合は問題が発生します
In [46]: data = {'AAA': [4, 5, 6, 7],
'BBB': [10, 20, 30, 40],
'CCC': [100, 50, -30, -50]}
# 注意:インデックスが1から始まっている
In [47]: df2 = pd.DataFrame(data=data, index=[1, 2, 3, 4])
In [48]: df2.iloc[1:3] # 位置指向
Out[48]:
AAA BBB CCC
2 5 20 50
3 6 30 -30
In [49]: df2.loc[1:3] # ラベル指向
Out[49]:
AAA BBB CCC
1 4 10 100
2 5 20 50
3 6 30 -30
比較演算子(〜)[Not]を使用して條件の否定で取得する
In [50]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
'BBB': [10, 20, 30, 40],
'CCC': [100, 50, -30, -50]})
In [51]: df
Out[51]:
AAA BBB CCC
0 4 10 100
1 5 20 50
2 6 30 -30
3 7 40 -50
In [52]: df[~((df.AAA <= 6) & (df.index.isin([0, 2, 4])))]
Out[52]:
AAA BBB CCC
1 5 20 50
3 7 40 -50
[コード リーディング]
# `~`を使わない条件で取得すると
df[((df.AAA <= 6) & (df.index.isin([0, 2, 4])))]
AAA | BBB | CCC | |
---|---|---|---|
0 | 4 | 10 | 100 |
2 | 6 | 30 | -30 |
New columns(新しい列)
applymap関数を使用して新しい列を効率的かつ動的に作成する
In [53]: df = pd.DataFrame({'AAA': [1, 2, 1, 3],
'BBB': [1, 1, 2, 2],
'CCC': [2, 1, 3, 1]})
In [54]: df
Out[54]:
AAA BBB CCC
0 1 1 2
1 2 1 1
2 1 2 3
3 3 2 1
In [55]: source_cols = df.columns # 既存の列名を取得
In [56]: new_cols = [str(x) + "_cat" for x in source_cols]
In [57]: categories = {1: 'Alpha', 2: 'Beta', 3: 'Charlie'}
In [58]: df[new_cols] = df[source_cols].applymap(categories.get)
In [59]: df
Out[59]:
AAA BBB CCC AAA_cat BBB_cat CCC_cat
0 1 1 2 Alpha Alpha Beta
1 2 1 1 Beta Alpha Alpha
2 1 2 3 Alpha Beta Charlie
3 3 2 1 Charlie Beta Alpha
[コード リーディング]
# 既存の列名を取得する
source_cols = df.columns
source_cols
Index(['AAA', 'BBB', 'CCC'], dtype='object')
# 既存の列名に`_cat`に付け加えたリストを作成する
new_cols = [str(x) + "_cat" for x in source_cols]
new_cols
['AAA_cat', 'BBB_cat', 'CCC_cat']
新しい列['AAA_cat', 'BBB_cat', 'CCC_cat']を既存の列に対してcategories
辞書からの値で更新する
groupbyでmin()を使用する場合、他の列を保持します
In [60]: df = pd.DataFrame({'AAA': [1, 1, 1, 2, 2, 2, 3, 3],
'BBB': [2, 1, 3, 4, 5, 1, 2, 3]})
In [61]: df
Out[61]:
AAA BBB
0 1 2
1 1 1
2 1 3
3 2 4
4 2 5
5 2 1
6 3 2
7 3 3
方法1:最小のインデックスを取得するidxmin()
In [62]: df.loc[df.groupby("AAA")["BBB"].idxmin()]
Out[62]:
AAA BBB
1 1 1
5 2 1
6 3 2
方法2:並べ替えてからそれぞれを最初に取得する
In [63]: df.sort_values(by="BBB").groupby("AAA", as_index=False).first()
Out[63]:
AAA BBB
0 1 1
1 2 1
2 3 2
[コード リーディング]
- 方法1
# 'AAA'列でグループ化する
gb = df.groupby("AAA")["BBB"]
# 'AAA'列の'2'を取得すると以下の内容になるので、'AAA'列の'2'の最少値は'1'となる
gb.get_group(2)
3 4
4 5
5 1
Name: BBB, dtype: int64
- 方法2
# 'BBB'列でソートする
sv = df.sort_values(by="BBB")
sv
AAA BBB
1 1 1
5 2 1
0 1 2
6 3 2
2 1 3
7 3 3
3 2 4
4 2 5
# 'AAA'列でグループ化する
gb = sv.groupby("AAA", as_index=False)
# 'AAA'列の'2'を取得すると最少値は'1'
gb.get_group(2)
AAA BBB
5 2 1
3 2 4
4 2 5
# グループオブジェクトの最初を取得する
gb.first()
AAA BBB
0 1 1
1 2 1
2 3 2
MultiIndexing(マルチインデックス)
ラベル付きフレームからMultiIndexを作成する
In [64]: df = pd.DataFrame({'row': [0, 1, 2],
'One_X': [1.1, 1.1, 1.1],
'One_Y': [1.2, 1.2, 1.2],
'Two_X': [1.11, 1.11, 1.11],
'Two_Y': [1.22, 1.22, 1.22]})
In [65]: df
Out[65]:
row One_X One_Y Two_X Two_Y
0 0 1.1 1.2 1.11 1.22
1 1 1.1 1.2 1.11 1.22
2 2 1.1 1.2 1.11 1.22
# ラベル付きインデックス
In [66]: df = df.set_index('row')
In [67]: df
Out[67]:
One_X One_Y Two_X Two_Y
row
0 1.1 1.2 1.11 1.22
1 1.1 1.2 1.11 1.22
2 1.1 1.2 1.11 1.22
# 既存の列名を'_'で分割しタプル形式で階層インデックスに変更する。
In [68]: df.columns = pd.MultiIndex.from_tuples([tuple(c.split('_')) for c in df.columns])
In [69]: df
Out[69]:
One Two
X Y X Y
row
0 1.1 1.2 1.11 1.22
1 1.1 1.2 1.11 1.22
2 1.1 1.2 1.11 1.22
# 階層インデックスを列データにし、インデックスをリセットする
In [70]: df = df.stack(0).reset_index(1)
In [71]: df
Out[71]:
level_1 X Y
row
0 One 1.10 1.20
0 Two 1.11 1.22
1 One 1.10 1.20
1 Two 1.11 1.22
2 One 1.10 1.20
2 Two 1.11 1.22
# ラベルを修正します(ラベル「level_1」が自動的に追加されたことに注意してください)
In [72]: df.columns = ['Sample', 'All_X', 'All_Y']
In [73]: df
Out[73]:
Sample All_X All_Y
row
0 One 1.10 1.20
0 Two 1.11 1.22
1 One 1.10 1.20
1 Two 1.11 1.22
2 One 1.10 1.20
2 Two 1.11 1.22
[コードリーディング]
# 内包表記で既存の列を'_'で分割する
[tuple(c.split('_')) for c in df.columns]
[('One', 'X'), ('One', 'Y'), ('Two', 'X'), ('Two', 'Y')]
# 階層インデックスはタプル形式で表す
df.columns = pd.MultiIndex.from_tuples([tuple(c.split('_')) for c in df.columns])
df.columns
MultiIndex([('One', 'X'),
('One', 'Y'),
('Two', 'X'),
('Two', 'Y')],
)
# 列から行へピボットする
df = df.stack(0)
X Y
row
0 One 1.10 1.20
Two 1.11 1.22
1 One 1.10 1.20
Two 1.11 1.22
2 One 1.10 1.20
Two 1.11 1.22
indexのLevel=1('One,Two')をリセットする
df.reset_index(1)
level_1 X Y
row
0 One 1.10 1.20
0 Two 1.11 1.22
1 One 1.10 1.20
1 Two 1.11 1.22
2 One 1.10 1.20
2 Two 1.11 1.22
## Arithmetic(算術)
* 以下のコードは、公式サイトから一部数値を変更しています。
```python
In [74]: cols = pd.MultiIndex.from_tuples([(x, y) for x in ['A', 'B', 'C'] for y in ['O', 'I']])
In [75]: df = pd.DataFrame(np.arange(12).reshape((2,6)), index=['n', 'm'], columns=cols)
In [76]: df
Out[76]:
A B C
O I O I O I
n 0 1 2 3 4 5
m 6 7 8 9 10 11
In [77]: df = df.div(df['C'], level=1)
In [78]: df
Out[78]:
A B C
O I O I O I
n 0.0 0.200000 0.5 0.600000 1.0 1.0
m 0.6 0.636364 0.8 0.818182 1.0 1.0
[コード リーディング]
$ n行をB列に説明すると $
$ [B][O](2 \div 4 = 0.5) $
$ [B][I](3 \div 5 = 0.600000) $
A | B | C | ||||
---|---|---|---|---|---|---|
O | I | O | I | O | I | |
n | 0.0 | 0.200000 | 0.5 | 0.600000 | 1.0 | 1.0 |
m | 0.6 | 0.636364 | 0.8 | 0.818182 | 1.0 | 1.0 |
Slice(スライス)
coords = [('AA', 'one'), ('AA', 'six'), ('BB', 'one'), ('BB', 'two'), ('BB', 'six')]
index = pd.MultiIndex.from_tuples(coords)
df = pd.DataFrame([11, 22, 33, 44, 55], index, ['MyData'])
MyData | ||
---|---|---|
AA | one | 11 |
six | 22 | |
BB | one | 33 |
two | 44 | |
six | 55 |
1番目のレベルと一番目の軸から選択するには
# 一番目の軸
df.xs('BB', level=0, axis=0)
MyData | |
---|---|
one | 33 |
two | 44 |
six | 55 |
'six'のレベルと一番目の軸から選択するには
# 2番目の軸
df.xs('six', level=1, axis=0)
MyData | |
---|---|
AA | 22 |
BB | 55 |
In [85]: import itertools
In [86]: index = list(itertools.product(['Ada', 'Quinn', 'Violet'],
['Comp', 'Math', 'Sci']))
In [87]: headr = list(itertools.product(['Exams', 'Labs'], ['I', 'II']))
In [88]: indx = pd.MultiIndex.from_tuples(index, names=['Student', 'Course'])
In [89]: cols = pd.MultiIndex.from_tuples(headr) # Notice these are un-named
In [90]: data = [[70 + x + y + (x * y) % 3 for x in range(4)] for y in range(9)]
In [91]: df = pd.DataFrame(data, indx, cols)
In [92]: df
Out[92]:
Exams Labs
I II I II
Student Course
Ada Comp 70 71 72 73
Math 71 73 75 74
Sci 72 75 75 75
Quinn Comp 73 74 75 76
Math 74 76 78 77
Sci 75 78 78 78
Violet Comp 76 77 78 79
Math 77 79 81 80
Sci 78 81 81 81
In [93]: All = slice(None)
In [94]: df.loc['Violet']
Out[94]:
Exams Labs
I II I II
Course
Comp 76 77 78 79
Math 77 79 81 80
Sci 78 81 81 81
In [95]: df.loc[(All, 'Math'), All]
Out[95]:
Exams Labs
I II I II
Student Course
Ada Math 71 73 75 74
Quinn Math 74 76 78 77
Violet Math 77 79 81 80
In [96]: df.loc[(slice('Ada', 'Quinn'), 'Math'), All]
Out[96]:
Exams Labs
I II I II
Student Course
Ada Math 71 73 75 74
Quinn Math 74 76 78 77
In [97]: df.loc[(All, 'Math'), ('Exams')]
Out[97]:
I II
Student Course
Ada Math 71 73
Quinn Math 74 76
Violet Math 77 79
In [98]: df.loc[(All, 'Math'), (All, 'II')]
Out[98]:
Exams Labs
II II
Student Course
Ada Math 73 74
Quinn Math 76 77
Violet Math 79 80
[コード リーディング]
All = slice(None) #これはどういう事?
# silice(start, stop, step)だから、Noneが3つあるのか。
All
slice(None, None, None)
print(type(All))
# <class 'slice'> # スライスオブジェクト
-
ALL = None
で試してみたがエラーになるので、抽出条件となるのはスライスオブジェクトでなければならないという事? - ここでは、
ALL
は条件指定なしという意味
indx
MultiIndex([( 'Ada', 'Comp'),
( 'Ada', 'Math'),
( 'Ada', 'Sci'),
( 'Quinn', 'Comp'),
( 'Quinn', 'Math'),
( 'Quinn', 'Sci'),
('Violet', 'Comp'),
('Violet', 'Math'),
('Violet', 'Sci')],
names=['Student', 'Course'])
cols
MultiIndex([('Exams', 'I'),
('Exams', 'II'),
( 'Labs', 'I'),
( 'Labs', 'II')],
)
Sorting(並び替え)
MultiIndexを使用して、特定の列または列の順序付きリストで並べ替える
df.sort_values(by=('Labs', 'II'), ascending=False)
Exams | Labs | ||||
---|---|---|---|---|---|
I | II | I | II | ||
Student | Course | ||||
Violet | Sci | 78 | 81 | 81 | 81 |
Math | 77 | 79 | 81 | 80 | |
Comp | 76 | 77 | 78 | 79 | |
Quinn | Sci | 75 | 78 | 78 | 78 |
Math | 74 | 76 | 78 | 77 | |
Comp | 73 | 74 | 75 | 76 | |
Ada | Sci | 72 | 75 | 75 | 75 |
Math | 71 | 73 | 75 | 74 | |
Comp | 70 | 71 | 72 | 73 |
Levels(レベル)
マルチインデックスの前にレベルを追加する
# このDFに'Firstlevel:Foo'を追加するには?
pd.concat([df], keys=['Foo'], names=['Firstlevel'])
Exams | Labs | |||||
---|---|---|---|---|---|---|
I | II | I | II | |||
Firstlevel | Student | Course | ||||
Foo | Ada | Comp | 70 | 71 | 72 | 73 |
Math | 71 | 73 | 75 | 74 | ||
Sci | 72 | 75 | 75 | 75 | ||
Quinn | Comp | 73 | 74 | 75 | 76 | |
Math | 74 | 76 | 78 | 77 | ||
Sci | 75 | 78 | 78 | 78 | ||
Violet | Comp | 76 | 77 | 78 | 79 | |
Math | 77 | 79 | 81 | 80 | ||
Sci | 78 | 81 | 81 | 81 |
階層インデックをフラットにするには
df.columns = df.columns.get_level_values(0)
Exams | Labs | ||||
---|---|---|---|---|---|
I | II | I | II | ||
Student | Course | ||||
Ada | Comp | 70 | 71 | 72 | 73 |
Math | 71 | 73 | 75 | 74 | |
Sci | 72 | 75 | 75 | 75 | |
Quinn | Comp | 73 | 74 | 75 | 76 |
Math | 74 | 76 | 78 | 77 | |
Sci | 75 | 78 | 78 | 78 | |
Violet | Comp | 76 | 77 | 78 | 79 |
Math | 77 | 79 | 81 | 80 | |
Sci | 78 | 81 | 81 | 81 |
Missing data(欠損値)
時系列の逆順を順方向のデータで埋める
In [100]: df = pd.DataFrame(np.random.randn(6, 1),
index=pd.date_range('2013-08-01', periods=6, freq='B'),
columns=list('A'))
In [101]: df.loc[df.index[3], 'A'] = np.nan
In [102]: df
Out[102]:
A
2013-08-01 0.721555
2013-08-02 -0.706771
2013-08-05 -1.039575
2013-08-06 NaN
2013-08-07 -0.424972
2013-08-08 0.567020
In [103]: df.reindex(df.index[::-1]).ffill()
Out[103]:
A
2013-08-08 0.567020
2013-08-07 -0.424972
2013-08-06 -0.424972
2013-08-05 -1.039575
2013-08-02 -0.706771
2013-08-01 0.721555
[コード リーディング]
df.reindex(df.index[::-1]).ffill()
df.reindex(df.index[::-1]).fillna(method='ffill') #上と同義
上記は、indexの逆順でインデックスを再セットしfiillna(method='ffill')
で穴埋めをする例です。fillna
関数ではmode='ffill'(前方向), 'bfill(後方向)'
を使って穴埋めをすることができるのでffill()
より汎用的です。
累積をNaNでリセットする
v = pd.Series([1, 1, 1, np.nan, 1, 1, 1, 1, np.nan, 1], dtype=float)
n = v.isnull()
a = ~n
c = a.astype(float).cumsum()
d = pd.Series(np.diff(np.hstack(([0.], c[n]))), index=index)
v[n] = -d
result = v.cumsum()
result
0 1.0
1 2.0
2 3.0
3 0.0
4 1.0
5 2.0
6 3.0
7 4.0
8 0.0
9 1.0
dtype: float64
[コード リーディング]
$(1)Nan値を含むデータを作成する$
$ v = pd.Series([1, 1, 1, np.nan, 1, 1, 1, 1, np.nan, 1], dtype=float) \tag{1} $
$(2)nにはNaN値をブール値で取得します$
$ n = v.isnull() \tag{2} $
$(3)aにはnのブール値を反転させる$
$ a = \tilde~n \tag{3} $
$(4)aのブール値基準で積算する $
$ c = a.astype(float).cumsum() \tag{4} $
$(5)積算したデータからnのFalseのインデックスを取得する$
$ index = c[n].index \tag{5} $
$(6)0からc[n]の差分を取得する $
$ d = pd.Series(np.diff(np.hstack(([0.], c[n]))), index=index) \tag{6} $
$(7)(6)で取得した数値をvのFalseにマイナス値で代入する $
$ v[n] = -d \tag{7} $
$(8)(7)のデータを積算するとマイナス値で0となる $
$ result = v.cumsum() \tag{8} $
\begin
{array}{|c|c|c|c|c|c|c|r|c|} \hline
& (1) & (2) & (3) & (4) &(5)&(6)& (7) & (8) \\ \hline
0 & 1.0 & False & True & 1.0 & & & 1.0 & 1.0 \\ \hline
1 & 1.0 & False & True & 2.0 & & & 1.0 & 2.0 \\ \hline
2 & 1.0 & False & True & 3.0 & & & 1.0 & 3.0 \\ \hline
3 & NaN & True & False & 3.0 & 3 & 3 & -3.0 & 0.0 \\ \hline
4 & 1.0 & False & True & 4.0 & & & 1.0 & 1.0 \\ \hline
5 & 1.0 & False & True & 5.0 & & & 1.0 & 2.0 \\ \hline
6 & 1.0 & False & True & 6.0 & & & 1.0 & 3.0 \\ \hline
7 & 1.0 & False & True & 7.0 & & & 1.0 & 4.0 \\ \hline
8 & NaN & True & False & 7.0 & 8 & 4 & -4.0 & 0.0 \\ \hline
9 & 1.0 & False & True & 8.0 & & & 1.0 & 1.0 \\ \hline
\end{array}
Grouping(グルーピング)
agg関数とは異なり、apply関数はすべての列にアクセスできるデータフレームを返します
df = pd.DataFrame({'animal': 'cat dog cat fish dog cat cat'.split(),
'size': list('SSMMMLL'),
'weight': [8, 10, 11, 1, 20, 12, 12],
'adult': [False] * 5 + [True] * 2})
In [105]: df
Out[105]:
animal size weight adult
0 cat S 8 False
1 dog S 10 False
2 cat M 11 False
3 fish M 1 False
4 dog M 20 False
5 cat L 12 True
6 cat L 12 True
# 最も重い動物のサイズをリストします。
In [106]: df.groupby('animal').apply(lambda subf: subf['size'][subf['weight'].idxmax()])
Out[106]:
animal
cat L
dog M
fish M
dtype: object
[コード リーディング]
# これを分解していくと
df.groupby('animal').apply(lambda subf: subf['size'][subf['weight'].idxmax()])
# 'weight'が最大値の要素のインデックスから'size'の要素を取得する
df['size'][df['weight'].idxmax()]
'M'
# なのでグループ化した中身のサイズの最大値が取得できる
get_group関数を使う
In [107]: gb = df.groupby(['animal'])
# 'get_group'を使って、GroupByオブジェクトの中身を確認する
In [108]: gb.get_group('cat')
Out[108]:
animal size weight adult
0 cat S 8 False
2 cat M 11 False
5 cat L 12 True
6 cat L 12 True
[コード リーディング]
# groupbyで取得したオブジェクトに対してのアクセスは
gb[['animal', 'size', 'weight']].get_group('cat')
animal | size | weight | |
---|---|---|---|
0 | cat | S | 8 |
2 | cat | M | 11 |
5 | cat | L | 12 |
6 | cat | L | 12 |
グループ内の異なるアイテムに適用する
In [109]: def GrowUp(x):
avg_weight = sum(x[x['size'] == 'S'].weight * 1.5)
avg_weight += sum(x[x['size'] == 'M'].weight * 1.25)
avg_weight += sum(x[x['size'] == 'L'].weight)
avg_weight /= len(x)
return pd.Series(['L', avg_weight, True], index=['size', 'weight', 'adult'])
In [110]: expected_df = gb.apply(GrowUp)
In [111]: expected_df
Out[111]:
size weight adult
animal
cat L 12.4375 True
dog L 20.0000 True
fish L 1.2500 True
[コード リーディング]
# S sizeの計算
cat_s = sum(df[(df['animal'] == 'cat') & (df['size'] == 'S')].weight * 1.5)
cat_s
12.0
# M sizeの計算
cat_m = sum(df[(df['animal'] == 'cat') & (df['size'] == 'M')].weight * 1.25)
cat_m
13.75
# L sizeの計算
cat_l = sum(df[(df['animal'] == 'cat') & (df['size'] == 'L')].weight)
cat_l
24
# 'SMLの平均
cat_avg = (cat_s + cat_m + cat_l) / len(df[df['animal'] == 'cat'])
cat_avg
12.4375
# 得られた結果をpd.Sriesの形式で返す
全件に関数を適用する
以下のコードは、公式サイトから一部数値を変更しています。
# S = pd.Series([i / 100.0 for i in range(1, 11)])
S = pd.Series([i for i in range(1, 11)])
S
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
dtype: int64
def cum_ret(x, y):
return x * (1 + y)
def red(x):
p = functools.reduce(cum_ret, x, 1.0)
return p
# すべてのデータに対して実行するexpanding().[mean(),std()...]
S.expanding().apply(red, raw=True)
0 2.0
1 6.0
2 24.0
3 120.0
4 720.0
5 5040.0
6 40320.0
7 362880.0
8 3628800.0
9 39916800.0
dtype: float64
[コード リーディング]
$ x=1 $
$ 1:red関数(x)が呼び出され、x=1.0となる。次に $
$ 2:cum\_ret関数(x,y)が呼び出され、x=1,y=1から x*(1+y)=1.0*(1.0+1.0)=2.0が返却される $
$ 3:p=2.0 $
$ x=2 $
$ p=6.0((12)(1+2)) $
$ x=3 $
$ p=24.0((123)*(1+3)) $
$ x=4 $
$ p=120.0((1234)(1+4)) $
0より小さい値を除いてグループの平均値に置き換える
In [116]: df = pd.DataFrame({'A': [1, 1, 2, 2], 'B': [1, -1, 1, 2]})
A B
0 1 1
1 1 -1
2 2 1
3 2 2
In [117]: gb = df.groupby('A')
In [118]: def replace(g):
mask = g < 0
return g.where(mask, g[~mask].mean())
In [119]: gb.transform(replace)
Out[119]:
B
0 1.0
1 -1.0
2 1.5
3 1.5
0より小さい値をグループの平均値に置き換える
df = pd.DataFrame({'A' : [1, 1, 1, 2, 2, 2], 'B' : [2, 3, -1, 1, 2, 6]})
gb = df.groupby('A')
def replace(g):
mask = g < 0
g.loc[mask] = g[~mask].mean()
return g
gb.transform(replace)
B
0 2.0
1 3.0
2 2.5
3 1.0
4 2.0
5 6.0
集計データでグループを並べ替える
In [120]: df = pd.DataFrame({'code': ['foo', 'bar', 'baz'] * 2,
'data': [0.16, -0.21, 0.33, 0.45, -0.59, 0.62],
'flag': [False, True] * 3})
In [121]: code_groups = df.groupby('code')
In [122]: agg_n_sort_order = code_groups[['data']].transform(sum).sort_values(by='data')
In [123]: sorted_df = df.loc[agg_n_sort_order.index]
In [124]: sorted_df
Out[124]:
code data flag
1 bar -0.21 True
4 bar -0.59 False
0 foo 0.16 False
3 foo 0.45 True
2 baz 0.33 False
5 baz 0.62 True
[コード リーディング]
# 'data'でグループ化して合計を求め並び替える。
agg_n_sort_order = code_groups[['data']].transform(sum).sort_values(by='data')
data
1 -0.80
4 -0.80
0 0.61
3 0.61
2 0.95
5 0.95
# 次にインデックスを求めて抽出する
agg_n_sort_order.index
Int64Index([1, 4, 0, 3, 2, 5], dtype='int64')
複数の集計列を作成する
In [125]: rng = pd.date_range(start="2014-10-07", periods=10, freq='2min')
In [126]: ts = pd.Series(data=list(range(10)), index=rng)
In [127]: def MyCust(x):
if len(x) > 2:
return x[1] * 1.234
return pd.NaT
In [128]: mhc = {'Mean': np.mean, 'Max': np.max, 'Custom': MyCust}
In [129]: ts.resample("5min").apply(mhc)
Out[129]:
Mean 2014-10-07 00:00:00 1
2014-10-07 00:05:00 3.5
2014-10-07 00:10:00 6
2014-10-07 00:15:00 8.5
Max 2014-10-07 00:00:00 2
2014-10-07 00:05:00 4
2014-10-07 00:10:00 7
2014-10-07 00:15:00 9
Custom 2014-10-07 00:00:00 1.234
2014-10-07 00:05:00 NaT
2014-10-07 00:10:00 7.404
2014-10-07 00:15:00 NaT
dtype: object
In [130]: ts
Out[130]:
2014-10-07 00:00:00 0
2014-10-07 00:02:00 1
2014-10-07 00:04:00 2
2014-10-07 00:06:00 3
2014-10-07 00:08:00 4
2014-10-07 00:10:00 5
2014-10-07 00:12:00 6
2014-10-07 00:14:00 7
2014-10-07 00:16:00 8
2014-10-07 00:18:00 9
Freq: 2T, dtype: int64
[コードリーディング]
# TimeSeries オブジェクトを'5min'に再集計する
ts_re = ts.resample('5min')
# オブジェクトの中身を見る
for t in ts_re:
print(t)
(Timestamp('2014-10-07 00:00:00', freq='5T'), 2014-10-07 00:00:00 0
2014-10-07 00:02:00 1
2014-10-07 00:04:00 2
Freq: 2T, dtype: int64)
(Timestamp('2014-10-07 00:05:00', freq='5T'), 2014-10-07 00:06:00 3
2014-10-07 00:08:00 4
Freq: 2T, dtype: int64)
(Timestamp('2014-10-07 00:10:00', freq='5T'), 2014-10-07 00:10:00 5
2014-10-07 00:12:00 6
2014-10-07 00:14:00 7
Freq: 2T, dtype: int64)
(Timestamp('2014-10-07 00:15:00', freq='5T'), 2014-10-07 00:16:00 8
2014-10-07 00:18:00 9
Freq: 2T, dtype: int64)
ts | data | mean | max | MyCust | x[1] | |
---|---|---|---|---|---|---|
0 | 2014-10-07 00:00:00 | 0,1,2 | 1 | 2 | 1.234 | 1 |
1 | 2014-10-07 00:05:00 | 3,4 | 3.5 | 4 | NaT | False |
2 | 2014-10-07 00:10:00 | 5,6,7 | 6 | 7 | 7.404 | 6 |
3 | 2014-10-07 00:15:00 | 8,9 | 8.5 | 9 | NaT | False |
値カウント列を作成し、DataFrameに再割り当てします
In [131]: df = pd.DataFrame({'Color': 'Red Red Red Blue'.split(),
'Value': [100, 150, 50, 50]})
In [132]: df
Out[132]:
Color Value
0 Red 100
1 Red 150
2 Red 50
3 Blue 50
In [133]: df['Counts'] = df.groupby(['Color']).transform(len)
In [134]: df
Out[134]:
Color Value Counts
0 Red 100 3
1 Red 150 3
2 Red 50 3
3 Blue 50 1
インデックスに基づいて列の値のグループをシフトする
In [135]: df = pd.DataFrame({'line_race': [10, 10, 8, 10, 10, 8],
'beyer': [99, 102, 103, 103, 88, 100]},
index=['Last Gunfighter', 'Last Gunfighter', 'Last Gunfighter',
'Paynter', 'Paynter', 'Paynter'])
In [136]: df
Out[136]:
line_race beyer
Last Gunfighter 10 99
Last Gunfighter 10 102
Last Gunfighter 8 103
Paynter 10 103
Paynter 10 88
Paynter 8 100
In [137]: df['beyer_shifted'] = df.groupby(level=0)['beyer'].shift(1)
In [138]: df
Out[138]:
line_race beyer beyer_shifted
Last Gunfighter 10 99 NaN
Last Gunfighter 10 102 99.0
Last Gunfighter 8 103 102.0
Paynter 10 103 NaN
Paynter 10 88 103.0
Paynter 8 100 88.0
各グループから最大値を持つ行を選択します
df = pd.DataFrame({'host': ['other', 'other', 'that', 'this', 'this'],
'service': ['mail', 'web', 'mail', 'mail', 'web'],
'no': [1, 2, 1, 2, 1]}).set_index(['host', 'service']
In [140]: mask = df.groupby(level=0).agg('idxmax')
In [141]: df_count = df.loc[mask['no']].reset_index()
In [142]: df_count
Out[142]:
host service no
0 other web 2
1 that mail 1
2 this mail 2
[コードリーディング]
no | ||
---|---|---|
host | service | |
other | 1 | |
web | 2 | |
that | 1 | |
this | 2 | |
web | 1 |
# level=0でグループ化したマスク値の中身を見る
mask = df.groupby(level=0).agg('idxmax')
no
host
other (other, web)
that (that, mail)
this (this, mail)
タプル形式でグループ化されたデータで最大値のインデックスが作成されている。
このマスク値で抽出した結果のデータフレームでインデックスをリセットしている。
itertools.groupbyのようなグループ化
In [143]: df = pd.DataFrame([0, 1, 0, 1, 1, 1, 0, 1, 1], columns=['A'])
In [144]: df['A'].groupby((df['A'] != df['A'].shift()).cumsum()).groups
Out[144]:
{1: Int64Index([0], dtype='int64'),
2: Int64Index([1], dtype='int64'),
3: Int64Index([2], dtype='int64'),
4: Int64Index([3, 4, 5], dtype='int64'),
5: Int64Index([6], dtype='int64'),
6: Int64Index([7, 8], dtype='int64')}
In [145]: df['A'].groupby((df['A'] != df['A'].shift()).cumsum()).cumsum()
Out[145]:
0 0
1 1
2 0
3 1
4 2
5 3
6 0
7 1
8 2
Name: A, dtype: int64
[コードリーディング]
# 上記のデータフレーム
df
A
0 0
1 1
2 0
3 1
4 1
5 1
6 0
7 1
8 1
- itertools.groupbyで同じデータを実行して見ると以下の内容になります。
data = [0, 1, 0, 1, 1, 1, 0, 1, 1]
gb = itertools.groupby(data)
for key, group in gb:
print(f'{key}: {list(group)}')
0: [0]
1: [1]
0: [0]
1: [1, 1, 1]
0: [0]
1: [1, 1]
- GroupBy.groupsは辞書形式でグループ名とラベルを返します。
tmp = df.groupby('A')
gb = tmp.groups
gb.get(1)
Int64Index([1, 3, 4, 5, 7, 8], dtype='int64')
gb.get(0)
Int64Index([0, 2, 6], dtype='int64')
$ (1)\ サンプルデータ $
$ df['A'] \tag{1} $
$ (2)\ データをシフトする $
$ df['A'].shift() \tag{2} $
$ (3)\ (2)と(3)が等価でない場合にTrue $
$ df['A'] != df['A'].shift() \tag{3} $
$ (4) \ (3)の累積を取る $
$ (df['A'] != df['A'].shift()).cumsum() \tag{4} $
$ (5)\ 累積データでグループ化したオブジェクトの中身 $
$ gb = df['A'].groupby((df['A'] != df['A'].shift()).cumsum()) \tag{5} $
$ (6)\ グループ化されたデータ累積する $
$ df['A'].groupby((df['A'] != df['A'].shift()).cumsum()).cumsum() \tag{6} $
\begin
{array}{|c|c|c|c|c|c|c|} \hline
& (1) & (2) & (3) & (4) & (5) & (6) \\ \hline
0 & 0 & NaN & True & 1 & 0 & 0 \\ \hline
1 & 1 & 0.0 & True & 2 & 1 & 1 \\ \hline
2 & 0 & 1.0 & True & 3 & 0 & 0 \\ \hline
3 & 1 & 0.0 & True & 4 & 1,1,1 & 1 \\ \hline
4 & 1 & 1.0 & False & 4 & & 2 \\ \hline
5 & 1 & 1.0 & False & 4 & & 3 \\ \hline
6 & 0 & 1.0 & True & 5 & 0 & 0 \\ \hline
7 & 1 & 0.0 & True & 6 & 1,1 & 1 \\ \hline
8 & 1 & 1.0 & False & 6 & & 2 \\ \hline
\end{array}
Expanding data(データの拡張)
カウントではなく値に基づくローリング計算
df = pd.DataFrame({'RollBasis':[1,1,1,2,3,5,8,10,12,13],
'ToRoll':[1,4,-5,2,-4,-2,0,-13,-2,-5]})
def f(x):
ser = df.ToRoll[(df.RollBasis >= x) & (df.RollBasis < x+5)]
return ser.sum()
df['Rolled'] = df.RollBasis.apply(f)
df
0 1 1 -4
1 1 4 -4
2 1 -5 -4
3 2 2 -4
4 3 -4 -6
5 5 -2 -2
6 8 0 -15
7 10 -13 -20
8 12 -2 -7
9 13 -5 -5
[コードリーディング]
RollBasis | ToRoll | |
---|---|---|
0 | 1 | 1 |
1 | 1 | 4 |
2 | 1 | -5 |
3 | 2 | 2 |
4 | 3 | -4 |
5 | 5 | -2 |
6 | 8 | 0 |
7 | 10 | -13 |
8 | 12 | -2 |
9 | 13 | -5 |
- 関数fの内容は、'RollBasis'の値がx以上かつx+5以下範囲で'ToRoll'の値を合計する。
$ x=1 $
$(RollBasis>=1) \& (RollBsis<1+5) \rightarrow sum([1,4,-5,2,-4,-2]) \rightarrow -4 $
$ \vdots $
$x =4 $
$(RollBasis>=4) \& (RollBsis<4+5) \rightarrow sum([-2,0]) \rightarrow -2 $
時間間隔ごとのローリング平均
df = pd.DataFrame({'B': range(5)})
df.index = [pd.Timestamp('20130101 09:00:00'),
pd.Timestamp('20130101 09:00:02'),
pd.Timestamp('20130101 09:00:03'),
pd.Timestamp('20130101 09:00:05'),
pd.Timestamp('20130101 09:00:06')]
df
2013-01-01 09:00:00 0
2013-01-01 09:00:02 1
2013-01-01 09:00:03 2
2013-01-01 09:00:05 3
2013-01-01 09:00:06 4
df.rolling(2, min_periods=1).sum()
B
2013-01-01 09:00:00 0.0
2013-01-01 09:00:02 1.0
2013-01-01 09:00:03 3.0
2013-01-01 09:00:05 5.0
2013-01-01 09:00:06 7.0
df.rolling('2s', min_periods=1).sum()
B
2013-01-01 09:00:00 0.0
2013-01-01 09:00:02 1.0
2013-01-01 09:00:03 3.0
2013-01-01 09:00:05 3.0
2013-01-01 09:00:06 7.0
[コードリーディング]
# rolling(window幅, 最少データ個数)
df.rolling(2, min_periods=1).sum()
# 0+1=1, 1+2=3, 2+3=5, 3+4=7 2行単位で
df.rolling('2s', min_periods=1).sum()
# 0+1=1, 1+2=3, 3+0=3, 3+4=7
# 時系列データを2分間隔にして合計を取って見ると
df.resample('2s').sum()
B
2013-01-01 09:00:00 0
2013-01-01 09:00:02 3
2013-01-01 09:00:04 3
2013-01-01 09:00:06 4
ローリング関数で時系列単位で合計から累計平均を求める
df = pd.DataFrame({'enddate':pd.to_datetime(['2019-03-01','2019-03-01','2019-03-01','2019-03-02','2019-03-02','2019-03-02','2019-03-03','2019-03-03','2019-03-04']),
'bloom':[0.342, 0.235, 0.456, 0.389, 0.453, 0.367, 0.369, 0.428, 0.55],
'unbloom':[0.658, 0.765, 0.544, 0.611, 0.547, 0.633, 0.631, 0.572, 0.45]})
Splitting(分割)
データフレームのリストを作成し、行に含まれる値を基準に分割します。
In [146]: d df = pd.DataFrame(data={'Case': ['A', 'A', 'A', 'B', 'A', 'A', 'B', 'A', 'A'],
'Data': np.random.randint(10,50,(9,))})
In [147]: dfs = list(zip(*df.groupby((1 * (df['Case'] == 'B')).cumsum()
.rolling(window=3, min_periods=1).median())))[-1]
In [148]: dfs[0]
Out[148]:
Case Data
0 A 24
1 A 34
2 A 33
3 B 11
In [149]: dfs[1]
Out[149]:
Case Data
4 A 31
5 A 35
6 B 13
In [150]: dfs[2]
Out[150]:
Case Data
7 A 17
8 A 33
[コードリーディング]
Case | Data | |
---|---|---|
0 | A | 24 |
1 | A | 34 |
2 | A | 33 |
3 | B | 11 |
4 | A | 31 |
5 | A | 35 |
6 | B | 13 |
7 | A | 17 |
8 | A | 33 |
dfs = list(zip(*df.groupby((1 * (df['Case'] == 'B')).cumsum().rolling(window=3, min_periods=1).median())))[-1]
- このワンライナーを分解していきます。
# 'Case'列が'B'を1とする
1 * (df['Case'] == 'B')
>
0 0
1 0
2 0
3 1
4 0
5 0
6 1
7 0
8 0
Name: Case, dtype: int64
# 累積を取る
1 * (df['Case'] == 'B').cumsum()
>
0 0
1 0
2 0
3 1
4 1
5 1
6 2
7 2
8 2
Name: Case, dtype: int64
# 累積値を3行単位で中央値を取る
(1 * (df['Case'] == 'B')).cumsum().rolling(window=3, min_periods=1).median()
>
0 0.0
1 0.0
2 0.0
3 0.0
4 1.0
5 1.0
6 1.0
7 2.0
8 2.0
Name: Case, dtype: float64
# 前の処理でグループ化する基準値ができたので、それで抽出しします。得られたグループオブジェクトを展開しリスト化します。
tmp2 = list(zip(*df.groupby((1 * (df['Case'] == 'B')).cumsum().rolling(window=3, min_periods=1).median())))
>
[(0.0, 1.0, 2.0), ( Case Data
0 A 24
1 A 34
2 A 33
3 B 11, Case Data
4 A 31
5 A 35
6 B 13, Case Data
7 A 17
8 A 33)]
# 得られたグループオブジェクトを展開しない場合の内容は、以下のようになりますのでアンパック(*)との違いがよくわかります。
tmp = list(zip(df.groupby((1 * (df['Case'] == 'B')).cumsum().rolling(window=3, min_periods=1).median())))
>
[((0.0, Case Data
0 A 24
1 A 34
2 A 33
3 B 11),), ((1.0, Case Data
4 A 31
5 A 35
6 B 13),), ((2.0, Case Data
7 A 17
8 A 33),)]
# dfsはリストで[(0.0,1.0,2.0),(以下のデータ)]なので[-1]として最後から1つ目の要素を取得する。
dfs = list(zip(*df.groupby((1 * (df['Case'] == 'B')).cumsum().rolling(window=3, min_periods=1).median())))[-1]
>
( Case Data
0 A 24
1 A 34
2 A 33
3 B 11, Case Data
4 A 31
5 A 35
6 B 13, Case Data
7 A 17
8 A 33)
Pivot(ピボット)
部分合計と小計
In [151]: df = pd.DataFrame(data={'Province': ['ON', 'QC', 'BC', 'AL', 'AL', 'MN', 'ON'],
'City': ['Toronto', 'Montreal', 'Vancouver',
'Calgary', 'Edmonton', 'Winnipeg', 'Windsor'],
'Sales': [13, 6, 16, 8, 4, 3, 1]})
In [152]: table = pd.pivot_table(df, values=['Sales'], index=['Province'],
columns=['City'], aggfunc=np.sum, margins=True)
In [153]: table.stack('City')
Out[153]:
Sales
Province City
AL All 12.0
Calgary 8.0
Edmonton 4.0
BC All 16.0
Vancouver 16.0
... ...
All Montreal 6.0
Toronto 13.0
Vancouver 16.0
Windsor 1.0
Winnipeg 3.0
[20 rows x 1 columns]
[コードリーディング]
与えらたデータフレームは以下の内容です。
Province | City | Sales | |
---|---|---|---|
0 | ON | Toronto | 13 |
1 | QC | Montreal | 6 |
2 | BC | Vancouver | 16 |
3 | AL | Calgary | 8 |
4 | AL | Edmonton | 4 |
5 | MN | Winnipeg | 3 |
6 | ON | Windsor | 1 |
# pivot_table関数で表形式に変換します。
table = pd.pivot_table(df, values=['Sales'], index=['Province'], columns=['City'], aggfunc=np.sum, margins=True)
|||||||Sales|
|:-----------|------------:|-------------:|------------:|------------:|:-----------:|:----------:|:---------:|:-------:|
| Province | Calgary | Edmonton | Montreal| Toronto |Vancouver|Windsor |Winnipeg|All |
| AL | 8 | 4 | nan | nan | nan | nan | nan | 12 |
| BC | nan | nan | nan | nan | 16 | nan | nan | 16 |
| MN | nan | nan | nan | nan | nan | nan | 3 | 3 |
| ON | nan | nan | nan | 13 | nan | 1 | nan | 14 |
| QC | nan | nan | 6 | nan | nan | nan | nan | 6 |
| All | 8 | 4 | 6 | 13 | 16 | 1 | 3 | 51 |
これをstack関数で列方向のデータを行方向に変形します。
Rのplyrのような頻度表
In [154]: grades = [48, 99, 75, 80, 42, 80, 72, 68, 36, 78]
In [155]: df = pd.DataFrame({'ID': ["x%d" % r for r in range(10)],
'Gender': ['F', 'M', 'F', 'M', 'F',
'M', 'F', 'M', 'M', 'M'],
'ExamYear': ['2007', '2007', '2007', '2008', '2008',
'2008', '2008', '2009', '2009', '2009'],
'Class': ['algebra', 'stats', 'bio', 'algebra',
'algebra', 'stats', 'stats', 'algebra',
'bio', 'bio'],
'Participated': ['yes', 'yes', 'yes', 'yes', 'no',
'yes', 'yes', 'yes', 'yes', 'yes'],
'Passed': ['yes' if x > 50 else 'no' for x in grades],
'Employed': [True, True, True, False,
False, False, False, True, True, False],
'Grade': grades})
In [156]: df.groupby('ExamYear').agg({'Participated': lambda x: x.value_counts()['yes'],
'Passed': lambda x: sum(x == 'yes'),
'Employed': lambda x: sum(x),
'Grade': lambda x: sum(x) / len(x)})
Out[156]:
Participated Passed Employed Grade
ExamYear
2007 3 2 3 74.000000
2008 3 3 0 68.500000
2009 3 2 2 60.666667
[コードリーディング]
ID | Gender | ExamYear | Class | Participated | Passed | Employed | Grade | |
---|---|---|---|---|---|---|---|---|
0 | x0 | F | 2007 | algebra | yes | no | True | 48 |
1 | x1 | M | 2007 | stats | yes | yes | True | 99 |
2 | x2 | F | 2007 | bio | yes | yes | True | 75 |
3 | x3 | M | 2008 | algebra | yes | yes | False | 80 |
4 | x4 | F | 2008 | algebra | no | no | False | 42 |
5 | x5 | M | 2008 | stats | yes | yes | False | 80 |
6 | x6 | F | 2008 | stats | yes | yes | False | 72 |
7 | x7 | M | 2009 | algebra | yes | yes | True | 68 |
8 | x8 | M | 2009 | bio | yes | no | True | 36 |
9 | x9 | M | 2009 | bio | yes | yes | False | 78 |
- 日常的にSQLは使っていませんが、Sqlite3に入れてSQL文で確認しました。
select ExamYear,
count(Participated = 'yes' or null) as participated,
count(Passed = 'yes' or null) as passed,
count(Employed = 'True' or null) as employed,
sum(cast(Grade as real))/count(cast(Grade as real)) as grades
from df
group by ExamYear
;
"2007" "3" "2" "3" "74.0"
"2008" "3" "3" "0" "68.5"
"2009" "3" "2" "2" "60.6666666666667"
年と月のクロス集計を作成するには:
In [157]: df = pd.DataFrame({'value': np.random.randn(36)},
index=pd.date_range('2011-01-01', freq='M', periods=36))
In [158]: pd.pivot_table(df, index=df.index.month, columns=df.index.year,
values='value', aggfunc='sum')
Out[158]:
2011 2012 2013
1 -1.039268 -0.968914 2.565646
2 -0.370647 -1.294524 1.431256
3 -1.157892 0.413738 1.340309
4 -1.344312 0.276662 -1.170299
5 0.844885 -0.472035 -0.226169
6 1.075770 -0.013960 0.410835
7 -0.109050 -0.362543 0.813850
8 1.643563 -0.006154 0.132003
9 -1.469388 -0.923061 -0.827317
10 0.357021 0.895717 -0.076467
11 -0.674600 0.805244 -1.187678
12 -1.776904 -1.206412 1.130127
[コードリーディング]
# 1-12のみと思っていたインデックスはレコード分ありました。Yearも同じ。それぞれをunique()にして集約している。
df.index.month
>
Int64Index([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12],
dtype='int64')
>
df.index.year
>
Int64Index([2011, 2011, 2011, 2011, 2011, 2011, 2011, 2011, 2011, 2011, 2011,
2011, 2012, 2012, 2012, 2012, 2012, 2012, 2012, 2012, 2012, 2012,
2012, 2012, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013,
2013, 2013, 2013],
dtype='int64')
pivot_tableはエクセルと同様メモリを食うんだと思っていたら小さくなりました。考えてみたら変換前データを持ってないので当たり前でした。
df = pd.DataFrame({'value': np.random.randn(36)},
index=pd.date_range('2011-01-01', freq='M', periods=36))
df.__sizeof__()
576
df_pivot = pd.pivot_table(df, index=df.index.month, columns=df.index.year, values='value', aggfunc='sum')
df_pivot.__sizeof__()
384
Apply(更新)
整理にローリング適用-埋め込みリストをMultiIndexフレームに変換
In [159]: df = pd.DataFrame(data={'A': [[2, 4, 8, 16], [100, 200], [10, 20, 30]],
'B': [['a', 'b', 'c'], ['jj', 'kk'], ['ccc']]},
index=['I', 'II', 'III'])
In [160]: def SeriesFromSubList(aList):
return pd.Series(aList)
In [161]: df_orgz = pd.concat({ind: row.apply(SeriesFromSubList)
for ind, row in df.iterrows()})
In [162]: df_orgz
Out[162]:
0 1 2 3
I A 2 4 8 16.0
B a b c NaN
II A 100 200 NaN NaN
B jj kk NaN NaN
III A 10 20 30 NaN
B ccc NaN NaN NaN
[コードリーディング]
A | B | |
---|---|---|
I | [2, 4, 8, 16] | [a, b, c] |
II | [100, 200] | [jj, kk] |
III | [10, 20, 30] | [ccc] |
作成されたデータフレームのセルにあるリストを取得して、それをpd.Seriesに変換し結合する。最大列に足らないデータはNaNが追加される。
for ind, row in df.iterrows():
print('ind:'+str(ind))
print(row)
r = row.apply(SeriesFromSubList)
print(r)
>
ind:I
A [2, 4, 8, 16]
B [a, b, c]
Name: I, dtype: object
0 1 2 3
A 2 4 8 16.0
B a b c NaN
ind:II
A [100, 200]
B [jj, kk]
Name: II, dtype: object
0 1
A 100 200
B jj kk
ind:III
A [10, 20, 30]
B [ccc]
Name: III, dtype: object
0 1 2
A 10 20.0 30.0
B ccc NaN NaN
pd.Seriesからスカラー値に変換された結果を再度pd.Series形式に変換して、複数の列へのローリング関数を適用して
In [163]: df = pd.DataFrame(data=np.random.randn(2000, 2) / 10000,
index=pd.date_range('2001-01-01', periods=2000),
columns=['A', 'B'])
In [164]: df
Out[164]:
A B
2001-01-01 -0.000144 -0.000141
2001-01-02 0.000161 0.000102
2001-01-03 0.000057 0.000088
2001-01-04 -0.000221 0.000097
2001-01-05 -0.000201 -0.000041
... ... ...
2006-06-19 0.000040 -0.000235
2006-06-20 -0.000123 -0.000021
2006-06-21 -0.000113 0.000114
2006-06-22 0.000136 0.000109
2006-06-23 0.000027 0.000030
[2000 rows x 2 columns]
In [165]: def gm(df, const):
v = ((((df['A'] + df['B']) + 1).cumprod()) - 1) * const
return v.iloc[-1]
In [166]: s = pd.Series({df.index[i]: gm(df.iloc[i:min(i + 51, len(df) - 1)], 5)
for i in range(len(df) - 50)})
In [167]: s
Out[167]:
2001-01-01 0.000930
2001-01-02 0.002615
2001-01-03 0.001281
2001-01-04 0.001117
2001-01-05 0.002772
...
2006-04-30 0.003296
2006-05-01 0.002629
2006-05-02 0.002081
2006-05-03 0.004247
2006-05-04 0.003928
Length: 1950, dtype: float64
[コードリーディング]
# この例では50日単位にデータを抽出する。
# 抽出結果にこのgm関数を適用した戻り値を得る。
# 対象期間の先頭日時(この例では'2001-01-1')から、終了日時(2006-05-04)まで、日時と戻り値を保存する
>
# gm関数の機能:(df['A']+df['B']+1)の累積積を算出して1を引くその結果に定数:5を掛ける
gm(df.iloc[0:min(0 + 51, len(df) - 1)], 5)
0.000930
関数がスカラーの場合で複数の列にローリング適用する
In [168]: rng = pd.date_range(start='2014-01-01', periods=100)
In [169]: df = pd.DataFrame({'Open': np.random.randn(len(rng)),
'Close': np.random.randn(len(rng)),
'Volume': np.random.randint(100, 2000, len(rng))},
index=rng)
In [170]: df
Out[170]:
Open Close Volume
2014-01-01 -1.611353 -0.492885 1219
2014-01-02 -3.000951 0.445794 1054
2014-01-03 -0.138359 -0.076081 1381
2014-01-04 0.301568 1.198259 1253
2014-01-05 0.276381 -0.669831 1728
... ... ... ...
2014-04-06 -0.040338 0.937843 1188
2014-04-07 0.359661 -0.285908 1864
2014-04-08 0.060978 1.714814 941
2014-04-09 1.759055 -0.455942 1065
2014-04-10 0.138185 -1.147008 1453
[100 rows x 3 columns]
In [171]: def vwap(bars):
return ((bars.Close * bars.Volume).sum() / bars.Volume.sum())
In [172]: window = 5
In [173]: s = pd.concat([(pd.Series(vwap(df.iloc[i:i + window]),
index=[df.index[i + window]]))
for i in range(len(df) - window)])
In [174]: s.round(2)
Out[174]:
2014-01-06 0.02
2014-01-07 0.11
2014-01-08 0.10
2014-01-09 0.07
2014-01-10 -0.29
...
2014-04-06 -0.63
2014-04-07 -0.02
2014-04-08 -0.03
2014-04-09 0.34
2014-04-10 0.29
Length: 95, dtype: float64
[コードリーディング]
- window値5(5日単位)で集計したデータに対して、vmap関数を適用し結果を得る。
- 開始日にwindow値5を足して集計期間日として、集計期間日と結果のデータフレームを作成する。
[(pd.Series(vwap(df.iloc[i:i + window]),index=[df.index[i + window]])) for i in range(len(df) - window)]
# 一つ目の区間 i=0 として関数の内容を分解して見ると以下になります。
tmp = df.iloc[0:0 + window]
(tmp.Close*tmp.Volume).sum()
tmp.Volume.sum()
(tmp.Close*tmp.Volume).sum() / tmp.Volume.sum()
0.02
Timeseries(時系列)
時間区間
# indexer_between_time(start, end)
rng = pd.date_range('1/1/2000', periods=24, freq='H')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
2000-01-01 00:00:00 1.298558
2000-01-01 01:00:00 0.333122
2000-01-01 02:00:00 -0.034170
2000-01-01 03:00:00 -2.396508
2000-01-01 04:00:00 -0.608591
2000-01-01 05:00:00 1.710535
2000-01-01 06:00:00 -0.657193
2000-01-01 07:00:00 -1.166563
2000-01-01 08:00:00 1.768663
2000-01-01 09:00:00 1.395805
2000-01-01 10:00:00 -0.111062
2000-01-01 11:00:00 0.840320
2000-01-01 12:00:00 0.129081
2000-01-01 13:00:00 -1.604446
2000-01-01 14:00:00 -1.257967
2000-01-01 15:00:00 -0.384715
2000-01-01 16:00:00 -1.185482
2000-01-01 17:00:00 2.222229
2000-01-01 18:00:00 -0.917695
2000-01-01 19:00:00 -2.158507
2000-01-01 20:00:00 -0.487902
2000-01-01 21:00:00 -1.667997
2000-01-01 22:00:00 0.202186
2000-01-01 23:00:00 -0.710070
Freq: H, dtype: float64
>
# 10時から14時までを抽出する場合
ts.iloc[ts.index.indexer_between_time(datetime.time(10), datetime.time(14))]
2000-01-01 10:00:00 -0.111062
2000-01-01 11:00:00 0.840320
2000-01-01 12:00:00 0.129081
2000-01-01 13:00:00 -1.604446
2000-01-01 14:00:00 -1.257967
Freq: H, dtype: float64
indexにもとづく時系列のマスクについて
index = pd.date_range('2013-1-1',periods=10,freq='15Min')
data = pd.DataFrame(data=[1,2,3,4,5,6,7,8,9,0], columns=['value'], index=index)
data
value
2013-01-01 00:00:00 1
2013-01-01 00:15:00 2
2013-01-01 00:30:00 3
2013-01-01 00:45:00 4
2013-01-01 01:00:00 5
2013-01-01 01:15:00 6
2013-01-01 01:30:00 7
2013-01-01 01:45:00 8
2013-01-01 02:00:00 9
2013-01-01 02:15:00 0
data.index.indexer_between_time('01:15', '02:00')
array([5, 6, 7, 8])
data.iloc[data.index.indexer_between_time('1:15', '02:00')]
013-01-01 01:15:00 6
2013-01-01 01:30:00 7
2013-01-01 01:45:00 8
2013-01-01 02:00:00 9
[コードリーディング]
# 時間指定を逆にすると、除く期間が抽出される。
data.iloc[data.index.indexer_between_time('02:00', '1:15')]
value
2013-01-01 00:00:00 1
2013-01-01 00:15:00 2
2013-01-01 00:30:00 3
2013-01-01 00:45:00 4
2013-01-01 01:00:00 5
2013-01-01 01:15:00 6
2013-01-01 02:00:00 9
2013-01-01 02:15:00 0
週末を除外し、特定の時間のみを含む日時範囲を作成する
rng = pd.date_range('20130101 09:00','20130110 16:00',freq='30T')
DatetimeIndex(['2013-01-01 09:00:00', '2013-01-01 09:30:00',
'2013-01-01 10:00:00', '2013-01-01 10:30:00',
'2013-01-01 11:00:00', '2013-01-01 11:30:00',
'2013-01-01 12:00:00', '2013-01-01 12:30:00',
'2013-01-01 13:00:00', '2013-01-01 13:30:00',
...
'2013-01-10 11:30:00', '2013-01-10 12:00:00',
'2013-01-10 12:30:00', '2013-01-10 13:00:00',
'2013-01-10 13:30:00', '2013-01-10 14:00:00',
'2013-01-10 14:30:00', '2013-01-10 15:00:00',
'2013-01-10 15:30:00', '2013-01-10 16:00:00'],
dtype='datetime64[ns]', length=447, freq='30T')
# 不要な時間を除く
rng = rng.take(rng.indexer_between_time('09:30','16:00'))
DatetimeIndex(['2013-01-01 09:30:00', '2013-01-01 10:00:00',
'2013-01-01 10:30:00', '2013-01-01 11:00:00',
'2013-01-01 11:30:00', '2013-01-01 12:00:00',
'2013-01-01 12:30:00', '2013-01-01 13:00:00',
'2013-01-01 13:30:00', '2013-01-01 14:00:00',
...
'2013-01-10 11:30:00', '2013-01-10 12:00:00',
'2013-01-10 12:30:00', '2013-01-10 13:00:00',
'2013-01-10 13:30:00', '2013-01-10 14:00:00',
'2013-01-10 14:30:00', '2013-01-10 15:00:00',
'2013-01-10 15:30:00', '2013-01-10 16:00:00'],
dtype='datetime64[ns]', length=140, freq=None)
# 平日のみにする
rng = rng[rng.weekday<5]
DatetimeIndex(['2013-01-01 09:30:00', '2013-01-01 10:00:00',
'2013-01-01 10:30:00', '2013-01-01 11:00:00',
'2013-01-01 11:30:00', '2013-01-01 12:00:00',
'2013-01-01 12:30:00', '2013-01-01 13:00:00',
'2013-01-01 13:30:00', '2013-01-01 14:00:00',
...
'2013-01-10 11:30:00', '2013-01-10 12:00:00',
'2013-01-10 12:30:00', '2013-01-10 13:00:00',
'2013-01-10 13:30:00', '2013-01-10 14:00:00',
'2013-01-10 14:30:00', '2013-01-10 15:00:00',
'2013-01-10 15:30:00', '2013-01-10 16:00:00'],
dtype='datetime64[ns]', length=112, freq=None)
データフレームで行列のルックアップをする
以下のコードは、stackoverflow.comから一部数値を変更しています。
date_item = pd.to_datetime(['2019/1/4','2019/1/7','2019/1/7','2019/1/8','2019/1/9','2019/1/10','2019/1/10','2019/1/11','2019/1/15','2019/1/16','2019/1/16','2019/1/17','2019/1/18'])
DatetimeIndex(['2019-01-04', '2019-01-07', '2019-01-07', '2019-01-08',
'2019-01-09', '2019-01-10', '2019-01-10', '2019-01-11',
'2019-01-15', '2019-01-16', '2019-01-16', '2019-01-17',
'2019-01-18'],
dtype='datetime64[ns]', freq=None)
orders = pd.DataFrame({'Date':date_item,
'by':['buy','buy','sell','buy','sell','buy','buy','sell','buy','sell','buy','sell','buy'],
'cnt':[100, 200, 100, 50, 100, 100, 200, 50, 100, 50, 50, 50, 100],
'code':['1720','8086','4967','8086','8086','4967','1720','4967','1720','1720','8086','4967','8086'],
'prices':[1008, 1344, 7530, 1347, 1373, 7120, 1008, 7120, 995, 986, 1417, 6990, 1445]})
Prices
1720 | 8086 | 4967 | |
---|---|---|---|
2019-01-04 | 1008 | 1311 | 7370 |
2019-01-07 | 1033 | 1344 | 7530 |
2019-01-08 | 1025 | 1347 | 7130 |
2019-01-09 | 1034 | 1373 | 7460 |
2019-01-10 | 1008 | 1364 | 7120 |
2019-01-11 | 1000 | 1391 | 7030 |
2019-01-15 | 995 | 1400 | 6950 |
2019-01-16 | 986 | 1417 | 6920 |
2019-01-17 | 1002 | 1419 | 6990 |
2019-01-18 | 1010 | 1445 | 7050 |
Orders
Date | by | cnt | code | prices | |
---|---|---|---|---|---|
0 | 2019-01-04 | buy | 100 | 1720 | 1008 |
1 | 2019-01-07 | buy | 200 | 8086 | 1344 |
2 | 2019-01-07 | sell | 100 | 4967 | 7530 |
3 | 2019-01-08 | buy | 50 | 8086 | 1347 |
4 | 2019-01-09 | sell | 100 | 8086 | 1373 |
5 | 2019-01-10 | buy | 100 | 4967 | 7120 |
6 | 2019-01-10 | buy | 200 | 1720 | 1008 |
7 | 2019-01-11 | sell | 50 | 4967 | 7120 |
8 | 2019-01-15 | buy | 100 | 1720 | 995 |
9 | 2019-01-16 | sell | 50 | 1720 | 986 |
10 | 2019-01-16 | buy | 50 | 8086 | 1417 |
11 | 2019-01-17 | sell | 50 | 4967 | 6990 |
12 | 2019-01-18 | buy | 100 | 8086 | 1445 |
lookup(row_labels, col_labels)
prices.lookup(orders.Date, orders.code)
array([1008, 1344, 7530, 1347, 1373, 7120, 1008, 7030, 995, 986, 1417,
6990, 1445])
列が時間で行が日で構成されるデータフレームを、時系列の形式で連続した行シーケンスに変換します。
以下のコードは、stackoverflow.comの投稿内容から一部数値を変更しています。
df = pd.DataFrame({'Date':['2020-01-01', '2020-01-02'],'h1':[23,45],'h2':[18,17],'h3':[11,16],'h4':[29,31],'h24':[45,55]})
Date h1 h2 h3 h4 h24
0 2020-01-01 23 18 11 29 45
1 2020-01-02 45 17 16 31 55
# 以下のような形式に変換する
Date value
0 2020-01-01 23
2 2020-01-01 18
4 2020-01-01 11
6 2020-01-01 29
8 2020-01-01 45
1 2020-01-02 45
3 2020-01-02 17
5 2020-01-02 16
7 2020-01-02 31
9 2020-01-02 55
[コードリーディング]
# 横持ちから縦持ちに変換する
df = pd.melt(df, id_vars=['Date'])
>
Date variable value
0 2020-01-01 h1 23
1 2020-01-02 h1 45
2 2020-01-01 h2 18
3 2020-01-02 h2 17
4 2020-01-01 h3 11
5 2020-01-02 h3 16
6 2020-01-01 h4 29
7 2020-01-02 h4 31
8 2020-01-01 h24 45
9 2020-01-02 h24 55
# 列名変更
df = df.rename(columns={'variable': 'hour'})
>
Date hour value
0 2020-01-01 h1 23
1 2020-01-02 h1 45
2 2020-01-01 h2 18
3 2020-01-02 h2 17
4 2020-01-01 h3 11
5 2020-01-02 h3 16
6 2020-01-01 h4 29
7 2020-01-02 h4 31
8 2020-01-01 h24 45
9 2020-01-02 h24 55
# hour列の'h'の除去
df['hour'] = df['hour'].apply(lambda x: int(x.lstrip('h'))-1)
>
Date hour value
0 2020-01-01 0 23
1 2020-01-02 0 45
2 2020-01-01 1 18
3 2020-01-02 1 17
4 2020-01-01 2 11
5 2020-01-02 2 16
6 2020-01-01 3 29
7 2020-01-02 3 31
8 2020-01-01 23 45
9 2020-01-02 23 55
# 日と時間を結合した列データの作成
combined = df.apply(lambda x: pd.to_datetime(x['Date'], dayfirst=True) + datetime.timedelta(hours=int(x['hour'])), axis=1)
>
0 2020-01-01 00:00:00
1 2020-01-02 00:00:00
2 2020-01-01 01:00:00
3 2020-01-02 01:00:00
4 2020-01-01 02:00:00
5 2020-01-02 02:00:00
6 2020-01-01 03:00:00
7 2020-01-02 03:00:00
8 2020-01-01 23:00:00
9 2020-01-02 23:00:00
dtype: datetime64[ns]
# 'Date'列データの置き換え
df['Date'] = combined
Date hour value
0 2020-01-01 00:00:00 0 23
1 2020-01-02 00:00:00 0 45
2 2020-01-01 01:00:00 1 18
3 2020-01-02 01:00:00 1 17
4 2020-01-01 02:00:00 2 11
5 2020-01-02 02:00:00 2 16
6 2020-01-01 03:00:00 3 29
7 2020-01-02 03:00:00 3 31
8 2020-01-01 23:00:00 23 45
9 2020-01-02 23:00:00 23 55
# 'hour'列の削除
del df['hour']
>
# 日付順に並び替え
df = df.sort_values("Date")
>
Date value
0 2020-01-01 23
2 2020-01-01 18
4 2020-01-01 11
6 2020-01-01 29
8 2020-01-01 45
1 2020-01-02 45
3 2020-01-02 17
5 2020-01-02 16
7 2020-01-02 31
9 2020-01-02 55
時系列を特定の頻度に再インデックス化する際の重複の処理
# A時系列データの作成(日時+ミリ秒[ダミーデータ])->100件
intervals = np.random.randint(0,1000,size=100).cumsum()
df = pd.DataFrame({'time':[pd.Timestamp('20140101') + pd.offsets.Milli(i) for i in intervals ],
'value' : np.random.randn(len(intervals))})
# df
time value
0 2014-01-01 00:00:00.499 0.567731
1 2014-01-01 00:00:01.232 -0.751466
2 2014-01-01 00:00:01.238 1.250118
3 2014-01-01 00:00:01.533 0.588161
4 2014-01-01 00:00:02.210 0.996543
... ... ...
95 2014-01-01 00:00:49.216 0.753233
96 2014-01-01 00:00:49.719 0.849922
97 2014-01-01 00:00:49.732 1.171001
98 2014-01-01 00:00:50.723 -0.443835
99 2014-01-01 00:00:51.389 -0.830960
100 rows × 2 columns
# B時系列データのインデックス(日時+1分単位)->3601件
pd.date_range('20140101 00:00:00','20140101 01:00:00',freq='s')
DatetimeIndex(['2014-01-01 00:00:00', '2014-01-01 00:00:01',
'2014-01-01 00:00:02', '2014-01-01 00:00:03',
'2014-01-01 00:00:04', '2014-01-01 00:00:05',
'2014-01-01 00:00:06', '2014-01-01 00:00:07',
'2014-01-01 00:00:08', '2014-01-01 00:00:09',
...
'2014-01-01 00:59:51', '2014-01-01 00:59:52',
'2014-01-01 00:59:53', '2014-01-01 00:59:54',
'2014-01-01 00:59:55', '2014-01-01 00:59:56',
'2014-01-01 00:59:57', '2014-01-01 00:59:58',
'2014-01-01 00:59:59', '2014-01-01 01:00:00'],
dtype='datetime64[ns]', length=3601, freq='S')
# 結果
2014-01-01 00:00:00.000 NaN
2014-01-01 00:00:00.499 0.567731
2014-01-01 00:00:01.000 0.567731 <-元はNaN
2014-01-01 00:00:01.232 -0.751466
2014-01-01 00:00:01.238 1.250118
2014-01-01 00:00:01.533 0.588161
2014-01-01 00:00:02.000 0.588161 <-元はNaN
2014-01-01 00:00:02.210 0.996543
2014-01-01 00:00:02.652 0.322535
2014-01-01 00:00:03.000 0.322535 <-元はNaN
[コードリーディング]
- 大変わかりにくい見出しになっていますが、要はA時系列データに、別のB時系列(共に同じ時間軸内のものを)追加して、データ列が歯抜けになった状態をffill()で数値を埋めるテクニックです。
intervals
>
array([ 499, 1232, 1238, 1533, 2210, 2652, 3512, 3598, 3663,
3688, 3758, 3969, 4960, 5933, 6211, 7040, 7763, 8331,
9329, 10229, 10436, 10671, 10727, 11695, 12249, 13033, 13867,
13895, 14809, 15069, 16022, 16484, 16597, 17044, 17060, 17344,
18124, 18629, 19256, 20022, 20620, 21080, 21148, 21603, 22164,
22872, 23075, 23566, 24133, 24887, 24996, 25132, 25435, 26047,
26287, 27168, 27228, 28129, 29118, 29539, 30339, 30465, 31396,
31805, 32583, 33021, 33854, 34439, 34865, 35207, 35212, 35345,
35865, 36586, 37325, 37422, 38411, 38986, 39227, 39768, 40316,
41254, 42162, 42476, 43155, 43971, 44791, 44899, 45770, 46701,
47568, 47901, 48242, 48720, 48940, 49216, 49719, 49732, 50723,
51389])
>
# B時系列の作成し、A時系列のインデックスと結合する
rng = pd.date_range('20140101 00:00:00','20140101 01:00:00', freq='s')
new_range = pd.DatetimeIndex(np.append(rng, pd.Index(df.time)))
- stackoverflow.comでの投稿内容では、A時系列+B時系列となっていますがエラーになります。
- 調べて、DatetimeIndex(np.append(A,B))で対応できました。
# A時系列+B時系列のままなので、時系列で並び替え
new_range = new_range.sort_values()
# 'time'をインデックスにして結合した新しいインデックスに変更する
df.set_index('time').reindex(new_range).head()
>
2014-01-01 00:00:00.000 NaN
2014-01-01 00:00:00.499 0.567731
2014-01-01 00:00:01.000 NaN
2014-01-01 00:00:01.232 -0.751466
2014-01-01 00:00:01.238 1.250118
>
# ffill()で値を更新する
df.set_index('time').reindex(new_range).ffill().head(10)
>
value
2014-01-01 00:00:00.000 NaN
2014-01-01 00:00:00.499 0.567731
2014-01-01 00:00:01.000 0.567731
2014-01-01 00:00:01.232 -0.751466
2014-01-01 00:00:01.238 1.250118
2014-01-01 00:00:01.533 0.588161
2014-01-01 00:00:02.000 0.588161
2014-01-01 00:00:02.210 0.996543
2014-01-01 00:00:02.652 0.322535
2014-01-01 00:00:03.000 0.322535
Resampling(リサンプリング)
データフレームの単一の値を月間平均で割るにはどうすればよいですか?
df = pd.DataFrame(np.random.randint(1000, 1200, (12,1)), columns=['value'],
index = pd.date_range('2014-01-01 00:15:00', freq='15min', periods=12))
df
value
2014-01-01 00:15:00 1122
2014-01-01 00:30:00 1132
2014-01-01 00:45:00 1091
2014-01-01 01:00:00 1188
2014-01-01 01:15:00 1156
2014-01-01 01:30:00 1089
2014-01-01 01:45:00 1148
2014-01-01 02:00:00 1040
2014-01-01 02:15:00 1010
2014-01-01 02:30:00 1130
2014-01-01 02:45:00 1178
2014-01-01 03:00:00 1186
df['normed'] = df.groupby(grouper).transform(lambda x: x / x.mean())
df
value normed
Date
2014-01-01 00:15:00 1122 0.999555
2014-01-01 00:30:00 1132 1.008463
2014-01-01 00:45:00 1091 0.971938
2014-01-01 01:00:00 1188 1.058352
2014-01-01 01:15:00 1156 1.029844
2014-01-01 01:30:00 1089 0.970156
2014-01-01 01:45:00 1148 1.022717
2014-01-01 02:00:00 1040 0.926503
2014-01-01 02:15:00 1010 0.899777
2014-01-01 02:30:00 1130 1.006682
2014-01-01 02:45:00 1178 1.049443
2014-01-01 03:00:00 1186 1.056570
[コードリーディング]
df.index.name = 'Date'
# 特定の周期や間隔でグループ分けする場合はGrouperを使う。この場合は1ヶ月なので`freq='M'`とする。
grouper = pd.Grouper(level=0, freq='M')
>
# groupsプロパティでgrouperの内容を参照すると、末日の1ヶ月単位とわかります。
df.groupby(grouper).groups
{Timestamp('2014-01-31 00:00:00', freq='M'): 12}
>
# grouperを使わず、簡単に求められる手法が投稿されています。これは便利!
df.groupby([df.index.year, df.index.month]).transform(lambda x: x/x.mean())
MultiIndexを使用したグループ化
data = pd.concat([pd.DataFrame([['A']*36, list(pd.date_range('1/1/2011', periods=36, freq='H')), list(np.random.rand(36))], index = ['Group', 'Time', 'Value']).T,
pd.DataFrame([['B']*36, list(pd.date_range('1/1/2011', periods=36, freq='H')), list(np.random.rand(36))], index = ['Group', 'Time', 'Value']).T,
pd.DataFrame([['C']*36, list(pd.date_range('1/1/2011', periods=36, freq='H')), list(np.random.rand(36))], index = ['Group', 'Time', 'Value']).T],
axis = 0).set_index(['Group', 'Time'])
data
Value
Group Time
A 2011-01-01 00:00:00 0.306533
2011-01-01 01:00:00 0.653431
2011-01-01 02:00:00 0.0284997
2011-01-01 03:00:00 0.852406
2011-01-01 04:00:00 0.856705
... ... ...
C 2011-01-02 07:00:00 0.607041
2011-01-02 08:00:00 0.291705
2011-01-02 09:00:00 0.480091
2011-01-02 10:00:00 0.890153
2011-01-02 11:00:00 0.628454
108 rows × 1 columns
# 結果
Time Group
2011-01-01 A 0.497803
B 0.524934
C 0.566736
2011-01-02 A 0.442884
B 0.452267
C 0.632567
Name: Value, dtype: float64
[コードリーディング]
data['Value'] = data['Value'].astype(float)
# 2つのKey(Grouper A:日別, Grouper B:'Group')グルーピングして平均値を取る。
daily_counts = data.groupby([pd.Grouper(freq='D', level='Time'), pd.Grouper(level='Group')])['Value'].mean()
独自の期間でリサンプリングするには?
dtrange = pd.date_range(datetime.datetime(2013,1,1), datetime.datetime(2013,2,20))
df = pd.DataFrame({'p1': np.random.rand(len(dtrange)) + 5,
'p2': np.random.rand(len(dtrange)) + 10},
index=dtrange)
p1 p2
2013-01-01 5.428541 10.292222
2013-01-02 5.172898 10.077244
2013-01-03 5.414736 10.587493
2013-01-04 5.235626 10.567700
2013-01-05 5.659100 10.270270
2013-01-06 5.144520 10.033026
2013-01-07 5.340707 10.561371
2013-01-08 5.256909 10.647431
2013-01-09 5.491950 10.036358
2013-01-10 5.869493 10.010417
# 結果
df.groupby(date).mean()
p1 p2
2013-01-01 5.401448 10.308353
2013-01-11 5.362789 10.463397
2013-01-21 5.531094 10.346834
2013-02-01 5.559112 10.551347
2013-02-11 5.551023 10.451666
[コードリーディング]
# このdが上旬・中旬・下旬のインデックスを作成します。
d = df.index.day - np.clip((df.index.day - 1) // 10, 0, 2) * 10 - 1
d
Int64Index([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
dtype='int64')
# これはデータのインデックスの日を取得します。
df.index.day
Int64Index([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1, 2, 3,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
dtype='int64')
# インデックスを10日間単位にするために−1する。
df.index.day - 1
Int64Index([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
dtype='int64')
# 上旬、中旬、下旬のインデックスの作成
(df.index.day - 1) // 10
Int64Index([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1],
dtype='int64')
# np.clip(a, a_min, a_max, out=None, **kargs)
# np.clipは要素の値を任意の範囲内に収める処理をします。ここでは0~2までの範囲に収める
np.clip((df.index.day - 1) // 10, 0, 2)
Int64Index([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1],
dtype='int64')
# 上旬:0,中旬:10,下旬:20用に変換している
np.clip((df.index.day - 1) // 10, 0, 2) * 10
Int64Index([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10,
10, 10, 10, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
dtype='int64')
# dの算出を確認します。
tmp = pd.DataFrame({'A':df.index.day.values,
'B':np.clip((df.index.day - 1) // 10, 0, 2) * 10},
index=np.arange(51))
tmp['Diff'] = tmp['A'] - tmp['B']
tmp.head()
A B Diff
0 1 0 1
1 2 0 2
2 3 0 3
3 4 0 4
4 5 0 5
>
tmp['Diff'] = (tmp['A'] - tmp['B']) - 1
A B Diff
0 1 0 0
1 2 0 1
2 3 0 2
3 4 0 3
4 5 0 4
>
# 2020-01-01,2020-01-11,2020-01-21, 2020-02-01, 2020-02-11のタイムインデックスを作成する
# dをtimedelta形式に変換して、元の時系列から引くと、旬データのインデックスになります。
date = df.index.values - np.array(d, dtype='timedelta64[D]')
>
array(['2013-01-01T00:00:00.000000000', '2013-01-01T00:00:00.000000000',
'2013-01-01T00:00:00.000000000', '2013-01-01T00:00:00.000000000',
'2013-01-01T00:00:00.000000000', '2013-01-01T00:00:00.000000000',
'2013-01-01T00:00:00.000000000', '2013-01-01T00:00:00.000000000',
'2013-01-01T00:00:00.000000000', '2013-01-01T00:00:00.000000000',
'2013-01-11T00:00:00.000000000', '2013-01-11T00:00:00.000000000',
'2013-01-11T00:00:00.000000000', '2013-01-11T00:00:00.000000000',
'2013-01-11T00:00:00.000000000', '2013-01-11T00:00:00.000000000',
'2013-01-11T00:00:00.000000000', '2013-01-11T00:00:00.000000000',
'2013-01-11T00:00:00.000000000', '2013-01-11T00:00:00.000000000',
'2013-01-21T00:00:00.000000000', '2013-01-21T00:00:00.000000000',
'2013-01-21T00:00:00.000000000', '2013-01-21T00:00:00.000000000',
'2013-01-21T00:00:00.000000000', '2013-01-21T00:00:00.000000000',
'2013-01-21T00:00:00.000000000', '2013-01-21T00:00:00.000000000',
'2013-01-21T00:00:00.000000000', '2013-01-21T00:00:00.000000000',
'2013-01-21T00:00:00.000000000', '2013-02-01T00:00:00.000000000',
'2013-02-01T00:00:00.000000000', '2013-02-01T00:00:00.000000000',
'2013-02-01T00:00:00.000000000', '2013-02-01T00:00:00.000000000',
'2013-02-01T00:00:00.000000000', '2013-02-01T00:00:00.000000000',
'2013-02-01T00:00:00.000000000', '2013-02-01T00:00:00.000000000',
'2013-02-01T00:00:00.000000000', '2013-02-11T00:00:00.000000000',
'2013-02-11T00:00:00.000000000', '2013-02-11T00:00:00.000000000',
'2013-02-11T00:00:00.000000000', '2013-02-11T00:00:00.000000000',
'2013-02-11T00:00:00.000000000', '2013-02-11T00:00:00.000000000',
'2013-02-11T00:00:00.000000000', '2013-02-11T00:00:00.000000000',
'2013-02-11T00:00:00.000000000'], dtype='datetime64[ns]')
>
# この'date'をグループ化して平均を取ります。
df.groupby(date).mean()
p1 p2
2013-01-01 5.401448 10.308353
2013-01-11 5.362789 10.463397
2013-01-21 5.531094 10.346834
2013-02-01 5.559112 10.551347
2013-02-11 5.551023 10.451666
- 集計したい基準のDatetimeIndexを作成して、そのインデックス基準で集計する。
- このワンライナー処理は初心者には難解で、分解してみてなるほどでした。
新しい日を追加せずに取引時間を集約する
# 1分単位のデータで作成する
dates = pd.date_range('2014-01-01','2014-01-11', freq='T')[0:-1]
# 営業日のみとする
dates = dates[dates.dayofweek < 5]
s = pd.DataFrame(np.random.randn(dates.size), dates)
s
0
2014-01-01 00:00:00 -2.593328
2014-01-01 00:01:00 0.173850
2014-01-01 00:02:00 0.781819
2014-01-01 00:03:00 0.734917
2014-01-01 00:04:00 -1.323457
... ...
2014-01-10 23:55:00 0.158127
2014-01-10 23:56:00 1.205610
2014-01-10 23:57:00 -0.757652
2014-01-10 23:58:00 0.350570
2014-01-10 23:59:00 0.886426
11520 rows × 1 columns
# データフレームの日時を30分単位にリサンプリンングして平均を取る
s.groupby(lambda d: d.date()).resample('30min').mean()
0
2014-01-01 2014-01-01 00:00:00 -0.152332
2014-01-01 00:30:00 -0.172909
2014-01-01 01:00:00 0.110629
2014-01-01 01:30:00 -0.070501
2014-01-01 02:00:00 0.058135
... ... ...
2014-01-10 2014-01-10 21:30:00 -0.158843
2014-01-10 22:00:00 0.204738
2014-01-10 22:30:00 -0.057380
2014-01-10 23:00:00 0.212008
2014-01-10 23:30:00 -0.021065
384 rows × 1 columns
[コードリーディング]
30分単位でリサンプリングし、平均値を算出し日単位でグルーピングした。
件数は、初期は14,400件あり、営業日のみに抽出し11,520件となり30分単位に集約して、384件となる
日ごとのデータを月毎にグループ化する。
import random
df = pd.DataFrame({'string': [random.choice(('about', 'contact', 'home', 'blog')) for _ in range(120)],
'visits':np.random.randint(0,2000, (120,))},
index = pd.date_range('2001-01-01', freq='1d', periods=120))
re_df = df.groupby('string').resample('M').sum()
visits
string
about 2001-01-31 4879
2001-02-28 6713
2001-03-31 5747
2001-04-30 9286
blog 2001-01-31 9164
2001-02-28 8318
2001-03-31 4770
2001-04-30 6571
contact 2001-01-31 6883
2001-02-28 6177
2001-03-31 10228
2001-04-30 3531
home 2001-01-31 5496
2001-02-28 5261
2001-03-31 7083
2001-04-30 11383
[コードリーディング]
- random.choice('文字列A','文字列B','文字列C') ランダムに引数の文字列を抽出する。
- データを分類名でグルーピングし、'M'月単位でリサンプリングし合計する。
月ごとの構成比を新しい列に追加する
re_df['ratio'] = re_df.groupby(level=1).transform(lambda x: x / x.sum())
>
visits ratio
string
about 2001-01-31 4879 0.184657
2001-02-28 6713 0.253617
2001-03-31 5747 0.206519
2001-04-30 9286 0.301778
blog 2001-01-31 9164 0.346832
2001-02-28 8318 0.314254
2001-03-31 4770 0.171410
2001-04-30 6571 0.213545
contact 2001-01-31 6883 0.260503
2001-02-28 6177 0.233367
2001-03-31 10228 0.367543
2001-04-30 3531 0.114751
home 2001-01-31 5496 0.208008
2001-02-28 5261 0.198761
2001-03-31 7083 0.254528
2001-04-30 11383 0.369926
Merge(結合)
In [177]: rng = pd.date_range('2000-01-01', periods=6)
In [178]: df1 = pd.DataFrame(np.random.randn(6, 3), index=rng, columns=['A', 'B', 'C'])
In [179]: df2 = df1.copy()
In [180]: df = df1.append(df2, ignore_index=True)
In [181]: df
Out[181]:
A B C
0 -0.870117 -0.479265 -0.790855
1 0.144817 1.726395 -0.464535
2 -0.821906 1.597605 0.187307
3 -0.128342 -1.511638 -0.289858
4 0.399194 -1.430030 -0.639760
5 1.115116 -2.012600 1.810662
6 -0.870117 -0.479265 -0.790855
7 0.144817 1.726395 -0.464535
8 -0.821906 1.597605 0.187307
9 -0.128342 -1.511638 -0.289858
10 0.399194 -1.430030 -0.639760
11 1.115116 -2.012600 1.810662
[コードリーディング]
- 6件のデータを作成し、同じものを
copy()
して縦に結合する。 - 結合によっては、
ignore_index=True
が必要な場合がある。
DataFrameの自己結合
In [182]: df = pd.DataFrame(data={'Area': ['A'] * 5 + ['C'] * 2,
'Bins': [110] * 2 + [160] * 3 + [40] * 2,
'Test_0': [0, 1, 0, 1, 2, 0, 1],
'Data': np.random.randn(7)})
In [183]: df
Out[183]:
Area Bins Test_0 Data
0 A 110 0 0.632955
1 A 110 1 1.485463
2 A 160 0 -1.193891
3 A 160 1 -0.324484
4 A 160 2 1.293263
5 C 40 0 -0.476979
6 C 40 1 -0.467655
In [184]: df['Test_1'] = df['Test_0'] - 1
In [185]: pd.merge(df, df, left_on=['Bins', 'Area', 'Test_0'],
right_on=['Bins', 'Area', 'Test_1'],
suffixes=('_L', '_R'))
Out[185]:
Area Bins Test_0_L Data_L Test_1_L Test_0_R Data_R Test_1_R
0 A 110 0 0.632955 -1 1 1.485463 0
1 A 160 0 -1.193891 -1 1 -0.324484 0
2 A 160 1 -0.324484 0 2 1.293263 1
3 C 40 0 -0.476979 -1 1 -0.467655 0
[コードリーディング]
#
df['Test_1'] = df['Test_0'] - 1
>
Area Bins Test_0 Data Test_1
0 A 110 0 0.632955 -1
1 A 110 1 1.485463 0
2 A 160 0 -1.193891 -1
3 A 160 1 -0.324484 0
4 A 160 2 1.293263 1
5 C 40 0 -0.476979 -1
6 C 40 1 -0.467655 0
#### 値に基づいた条件で結合する:clipboard:
以下のコードは、stackoverfolw.comの投稿からから一部コードを変更しています。
```python
import operator as op
df_a = pd.DataFrame([{"a": 1, "b": 4}, {"a": 2, "b": 5}, {"a": 3, "b": 6}])
df_b = pd.DataFrame([{"c": 2, "d": 7}, {"c": 3, "d": 8}])
binOp = op.lt
matches = np.where(binOp(df_a.a[:,None],df_b.c.values))
pd.concat([df.ix[idxs].reset_index(drop=True)
for df,idxs in zip([df_a,df_b],matches)], axis=1)
df_a
a b
0 1 4
1 2 5
2 3 6
df_b
c d
0 2 7
1 3 8
# 結果
a b c d
0 1 4 2 7
1 1 4 3 8
2 2 5 3 8
[コードリーディング]
- まず
machtes
を分解していくと
# df.a列とdf.b列の内容を確認する
df_a.a[:,None]
array([[1],
[2],
[3]])
df_b.c.values
array([2, 3])
- 関数形式の標準演算子
operator.lt(a,b)
はa < b
で、a < b
と等価です。
op.lt(df_a.a[:,None],df_b.c.values)
array([[ True, True],
[False, True],
[False, False]])
# 別な書き方だとこのように書けた
df_a.a[:,None] < df_b.c.values
array([[ True, True],
[False, True],
[False, False]])
>
# この内容を確認するには
df_a.a[:,None][0] < df_b.c.values[0]
array([ True])
df_a.a[:,None][0] < df_b.c.values[1]
array([ True])
df_a.a[:,None][1] < df_b.c.values[0]
array([False])
df_a.a[:,None][1] < df_b.c.values[1]
array([ True])
df_a.a[:,None][2] < df_b.c.values[0]
array([False])
df_a.a[:,None][2] < df_b.c.values[1]
array([False])
>
# np.where(条件)の場合は、条件を満たす要素のインデックスをタプル形式で返します。
np.where([[True, True], [False, True], [False, False]])
(array([0, 0, 1]), array([0, 1, 1]))
>
# 先ほど'matches'で得られた条件で抽出します。
[df.loc[idxs].reset_index(drop=True) for df,idxs in zip([df_a,df_b],matches)]
[ a b
0 1 4
1 1 4
2 2 5, c d
0 2 7
1 3 8
2 3 8]
>
# これを横方向(axis=1)で結合する。
a b c d
0 1 4 2 7
1 1 4 3 8
2 2 5 3 8
Plotting(作図)
n [186]: df = pd.DataFrame(
.....: {'stratifying_var': np.random.uniform(0, 100, 20),
.....: 'price': np.random.normal(100, 5, 20)})
.....:
In [187]: df['quartiles'] = pd.qcut(
.....: df['stratifying_var'],
.....: 4,
.....: labels=['0-25%', '25-50%', '50-75%', '75-100%'])
.....:
In [188]: df.boxplot(column='price', by='quartiles')
Out[188]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc66b7f1710>
[コードリーディング]
# pandas.qcut(x, q, labels=None...)
# xをq:ビン分割数で分割し,labelsを付与する。
df['quartiles'] = pd.qcut(df['stratifying_var'], 4, labels=['0-25%', '25-50%', '50-75%', '75-100%'])
>
stratifying_var price quartiles
0 95.463259 110.468740 75-100%
1 61.567537 105.038334 50-75%
2 93.169189 99.502664 75-100%
3 32.881181 100.462400 25-50%
4 22.735506 95.821731 0-25%
5 85.662861 101.262124 75-100%
6 24.251856 102.351950 0-25%
7 26.323525 91.812003 25-50%
8 6.192982 111.425087 0-25%
9 25.520758 104.666583 25-50%
10 75.505473 104.450480 75-100%
11 30.620504 100.044772 25-50%
12 40.438555 102.697402 50-75%
13 18.171318 102.612876 0-25%
14 57.080747 101.067847 50-75%
15 44.066472 93.410125 50-75%
16 64.131972 103.707151 50-75%
17 33.548572 103.359709 25-50%
18 88.433754 99.416668 75-100%
19 21.660715 91.229785 0-25%
Data IN/Out(データの入出力)
複数のファイルを読み込んで単一のDataFrameを作成する
# A例
n [189]: for i in range(3):
data = pd.DataFrame(np.random.randn(10, 4))
data.to_csv('file_{}.csv'.format(i))
In [190]: files = ['file_0.csv', 'file_1.csv', 'file_2.csv']
In [191]: result = pd.concat([pd.read_csv(f) for f in files], ignore_index=True)
# B例
In [192]: import glob
In [193]: import os
In [194]: files = glob.glob('file_*.csv')
In [195]: result = pd.concat([pd.read_csv(f) for f in files], ignore_index=True)
[コードリーディング]
- A例はサンプルファイルをループで作成し、そのサンプルファイルを読み込んで結合する。
- B例は、作成済みのファイル名をglob関数で読み取って、そのファイルを読み込んで結合する。
複数列の日付コンポーネントの解析
In [196]: i = pd.date_range('20000101', periods=10000)
In [197]: df = pd.DataFrame({'year': i.year, 'month': i.month, 'day': i.day})
In [198]: df.head()
Out[198]:
year month day
0 2000 1 1
1 2000 1 2
2 2000 1 3
3 2000 1 4
4 2000 1 5
In [199]: %timeit pd.to_datetime(df.year * 10000 + df.month * 100 + df.day, format='%Y%m%d')
ds = df.apply(lambda x: "%04d%02d%02d" % (x['year'],
x['month'], x['day']), axis=1)
ds.head()
%timeit pd.to_datetime(ds)
9.98 ms +- 235 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
2.91 ms +- 57.5 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
[コードリーディング]
# 作成したデータフレームの値を使って、時系列情報を計算して作成する場合
df.year * 10000 + df.month * 100 + df.day
>
# 作成したデータフレームの要素から関数で時系列情報を作成する場合
ds = df.apply(lambda x: "%04d%02d%02d" % (x['year'], x['month'], x['day']), axis=1)
計算コストの違い?
ヘッダーとデータの間の行をスキップする
In [200]: data = """;;;;
;;;;
;;;;
;;;;
;;;;
;;;;
;;;;
;;;;
;;;;
;;;;
date;Param1;Param2;Param4;Param5
;m²;°C;m²;m
;;;;
01.01.1990 00:00;1;1;2;3
01.01.1990 01:00;5;3;4;5
01.01.1990 02:00;9;5;6;7
01.01.1990 03:00;13;7;8;9
01.01.1990 04:00;17;9;10;11
01.01.1990 05:00;21;11;12;13
"""
Option 1: 行を明示的に渡して行をスキップする
In [201]: from io import StringIO
In [202]: pd.read_csv(StringIO(data), sep=';', skiprows=[11, 12],
Out[202]:
Param1 Param2 Param4 Param5
date
1990-01-01 00:00:00 1 1 2 3
1990-01-01 01:00:00 5 3 4 5
1990-01-01 02:00:00 9 5 6 7
1990-01-01 03:00:00 13 7 8 9
1990-01-01 04:00:00 17 9 10 11
1990-01-01 05:00:00 21 11 12 13
Option 2: 列名を読み取ってからデータを読み取る
In [203]: pd.read_csv(StringIO(data), sep=';', header=10, nrows=10).columns
Out[203]: Index(['date', 'Param1', 'Param2', 'Param4', 'Param5'], dtype='object')
In [204]: columns = pd.read_csv(StringIO(data), sep=';', header=10, nrows=10).columns
In [205]: pd.read_csv(StringIO(data), sep=';', index_col=0,
header=12, parse_dates=True, names=columns)
Out[205]:
Param1 Param2 Param4 Param5
date
1990-01-01 00:00:00 1 1 2 3
1990-01-01 01:00:00 5 3 4 5
1990-01-01 02:00:00 9 5 6 7
1990-01-01 03:00:00 13 7 8 9
1990-01-01 04:00:00 17 9 10 11
1990-01-01 05:00:00 21 11 12 13
[コードリーディング]
データの大小に関わらずどちらかが有利とはないのではないかと思う。何れにしてもスキップする行数やヘッダー行に相当する行を見つける必要があるので、一度は適当な行数をスキップするなりして開くか、別途先にエディタで開く必要があります。
Computation(計算)
相関
In [211]: df = pd.DataFrame(np.random.random(size=(100, 5)))
In [212]: corr_mat = df.corr()
In [213]: mask = np.tril(np.ones_like(corr_mat, dtype=np.bool), k=-1)
In [214]: corr_mat.where(mask)
Out[214]:
0 1 2 3 4
0 NaN NaN NaN NaN NaN
1 -0.018923 NaN NaN NaN NaN
2 -0.076296 -0.012464 NaN NaN NaN
3 -0.169941 -0.289416 0.076462 NaN NaN
4 0.064326 0.018759 -0.084140 -0.079859 NaN
[コードリーディング]
mask
array([[False, False, False, False, False],
[ True, False, False, False, False],
[ True, True, False, False, False],
[ True, True, True, False, False],
[ True, True, True, True, False]])
>
mask2 = np.triu(np.ones_like(corr_mat, dtype=np.bool), k=-1)
mask2
array([[ True, True, True, True, True],
[ True, True, True, True, True],
[False, True, True, True, True],
[False, False, True, True, True],
[False, False, False, True, True]])
>
corr_mat.where(mask2)
>
0 1 2 3 4
0 1.000000 -0.022615 0.047486 0.092043 -0.112379
1 -0.022615 1.000000 -0.336864 -0.025473 0.004696
2 NaN -0.336864 1.000000 0.047746 -0.008458
3 NaN NaN 0.047746 1.000000 0.133289
4 NaN NaN NaN 0.133289 1.000000
numpy.tril() -> Numpy配列から下三角行列を抽出する(Lower triangle of an array.)
numpy.triu() -> Numpy配列から上三角行列を抽出する(Upper triangle of an array.)
Timedeltas(経過時間)
In [218]: import datetime
In [219]: s = pd.Series(pd.date_range('2012-1-1', periods=3, freq='D'))
In [220]: s - s.max()
Out[220]:
0 -2 days
1 -1 days
2 0 days
dtype: timedelta64[ns]
In [221]: s.max() - s
Out[221]:
0 2 days
1 1 days
2 0 days
dtype: timedelta64[ns]
In [222]: s - datetime.datetime(2011, 1, 1, 3, 5)
Out[222]:
0 364 days 20:55:00
1 365 days 20:55:00
2 366 days 20:55:00
dtype: timedelta64[ns]
In [223]: s + datetime.timedelta(minutes=5)
Out[223]:
0 2012-01-01 00:05:00
1 2012-01-02 00:05:00
2 2012-01-03 00:05:00
dtype: datetime64[ns]
In [224]: datetime.datetime(2011, 1, 1, 3, 5) - s
Out[224]:
0 -365 days +03:05:00
1 -366 days +03:05:00
2 -367 days +03:05:00
dtype: timedelta64[ns]
In [225]: datetime.timedelta(minutes=5) + s
Out[225]:
0 2012-01-01 00:05:00
1 2012-01-02 00:05:00
2 2012-01-03 00:05:00
dtype: datetime64[ns]
[コードリーディング]
# 2週間後
s + datetime.timedelta(weeks=2)
>
0 2012-01-15
1 2012-01-16
2 2012-01-17
dtype: datetime64[ns]
# 2週間前
s + datetime.timedelta(weeks=-2)
>
0 2011-12-18
1 2011-12-19
2 2011-12-20
dtype: datetime64[ns]
経過時間と日付の加算と減算
In [226]: deltas = pd.Series([datetime.timedelta(days=i) for i in range(3)])
In [227]: df = pd.DataFrame({'A': s, 'B': deltas})
In [228]: df
Out[228]:
A B
0 2012-01-01 0 days
1 2012-01-02 1 days
2 2012-01-03 2 days
In [229]: df['New Dates'] = df['A'] + df['B']
In [230]: df['Delta'] = df['A'] - df['New Dates']
In [231]: df
Out[231]:
A B New Dates Delta
0 2012-01-01 0 days 2012-01-01 0 days
1 2012-01-02 1 days 2012-01-03 -1 days
2 2012-01-03 2 days 2012-01-05 -2 days
In [232]: df.dtypes
Out[232]:
A datetime64[ns]
B timedelta64[ns]
New Dates datetime64[ns]
Delta timedelta64[ns]
dtype: object
[コードリーディング]
# 週の加算を実行する
w_deltas = pd.Series([datetime.timedelta(weeks=i) for i in range(3)])
w_deltas
0 0 days
1 7 days
2 14 days
dtype: timedelta64[ns]
>
df['W_Delta'] = df['A'] + w_deltas
>
A B New Dates Delta W_Delta
0 2012-01-01 0 days 2012-01-01 0 days 2012-01-01
1 2012-01-02 1 days 2012-01-03 -1 days 2012-01-09
2 2012-01-03 2 days 2012-01-05 -2 days 2012-01-17
軸名のエイリアス
軸名のエイリアスをグローバルに提供するには、次の2つの関数を定義できます。
def set_axis_alias(cls, axis, alias):
if axis not in cls._AXIS_NUMBERS:
raise Exception("invalid axis [%s] for alias [%s]" % (axis, alias))
cls._AXIS_ALIASES[alias] = axis
def clear_axis_alias(cls, axis, alias):
if axis not in cls._AXIS_NUMBERS:
raise Exception("invalid axis [%s] for alias [%s]" % (axis, alias))
cls._AXIS_ALIASES.pop(alias, None)
set_axis_alias(pd.DataFrame, 'columns', 'myaxis2')
df2 = pd.DataFrame(np.random.randn(3, 2), columns=['c1', 'c2'],
index=['i1', 'i2', 'i3'])
In [241]: df2.sum(axis='myaxis2')
Out[241]:
i1 -0.461013
i2 2.040016
i3 0.904681
dtype: float64
In [242]: clear_axis_alias(pd.DataFrame, 'columns', 'myaxis2')
[コードリーディング]
# df2.sum(axis='myaxis2')ではエラーが発生した。
df2.sum(axis=1)
この関数は、独自の軸名を定義することができると思うが、実際にはエラーが発生して結果は得られず。axis=1
を指定して得られた。
Creating example data(サンプルデータの作成)
In [243]: def expand_grid(data_dict):
rows = itertools.product(*data_dict.values())
return pd.DataFrame.from_records(rows, columns=data_dict.keys())
In [244]: df = expand_grid({'height': [60, 70],
'weight': [100, 140, 180],
'sex': ['Male', 'Female']})
In [245]: df
Out[245]:
height weight sex
0 60 100 Male
1 60 100 Female
2 60 140 Male
3 60 140 Female
4 60 180 Male
5 60 180 Female
6 70 100 Male
7 70 100 Female
8 70 140 Male
9 70 140 Female
10 70 180 Male
11 70 180 Female
サンプルデータの作成
- 特定の値のすべての組み合わせからデータフレームを作成するために、キーが列名で値がデータ値のリストである辞書を作成できます。
In [243]: def expand_grid(data_dict):
rows = itertools.product(*data_dict.values())
return pd.DataFrame.from_records(rows, columns=data_dict.keys())
In [244]: df = expand_grid({'height': [60, 70],
'weight': [100, 140, 180],
'sex': ['Male', 'Female']})
In [245]: df
Out[245]:
height weight sex
0 60 100 Male
1 60 100 Female
2 60 140 Male
3 60 140 Female
4 60 180 Male
5 60 180 Female
6 70 100 Male
7 70 100 Female
8 70 140 Male
9 70 140 Female
10 70 180 Male
11 70 180 Female
[コードリーディング]
# *dict.values()は辞書形式の値を展開する
d_dict = {'height': [60, 70], 'weight': [100, 140, 180], 'sex': ['Male', 'Female']}
print(*d_dict.values())
[60, 70] [100, 140, 180] ['Male', 'Female']
>
# 全ての組み合わせ直積(デカルト積) itertools.productはイテレータ
rows = itertools.product(*d_dict.values())
>
for r in rows:
print(r)
>
(60, 100, 'Male')
(60, 100, 'Female')
(60, 140, 'Male')
(60, 140, 'Female')
(60, 180, 'Male')
(60, 180, 'Female')
(70, 100, 'Male')
(70, 100, 'Female')
(70, 140, 'Male')
(70, 140, 'Female')
(70, 180, 'Male')
(70, 180, 'Female')
あとがき
- 最後までご覧いただきありがとうございました。Python言語学び始めて約2年半程度で、公式クックブックの自習はまさに苦闘でした。
- 1回目は、公式と同じようなデータを自ら用意して始めましたが、途中でサンプルデータが考えられず断念しました。
- 2回目は、公式を引用する形で、コードリーディング部分を折りたたみ機能で作成してみましたが見た目苦闘感が伝わらないとボツにしました。
- 言語の正確な理解と記述がままならない中で、十分な説明になっていないところはご容赦ください。
- 終わってみての実感は、**「インデックスを制する者は、Pandasを制する」**と思いました。
- コードリーディングでの説明は、mathjaxを初めて使いましたが、効果があったかは不明です。