Pandas の Series オブジェクトと DataFrame オブジェクト の四則演算(足し算)した時の挙動についていろいろ試してみた

Pandas の Series オブジェクトと DataFrame オブジェクト の四則演算(足算)の挙動を調べてみました。結果、 Series オブジェクト の index のラベルと DataFrame オブジェクト の columns のラベルがマッチする時、計算が行われることがわかりました。また計算は numpy 風に行われることがわかりました。説明が難しいため、コードだけ貼り付けておきます。

import pandas as pd
import numpy as np

np.random.seed(777)
s_data  = np.random.randint(0,5,5)
s_index = list('ABCDE')
s = pd.Series(s_data, index=s_index)

s
# In [243]: s
# Out[243]: 
# A    3
# B    1
# C    4
# D    1
# E    2
# dtype: int64

d_data  = np.random.randint(0,10,(3, 5))
d_columns = list('ABCDE')
d = pd.DataFrame(d_data, columns=d_columns)

d
# In [244]: d
# Out[244]: 
#    A  B  C  D  E
# 0  8  7  2  0  1
# 1  2  4  5  7  1
# 2  7  2  2  7  4

s + d
# In [245]: s + d
# Out[249]: 
#     A  B  C  D  E
# 0  11  8  6  1  3
# 1   5  5  9  8  3
# 2  10  3  6  8  6

d + s
# In [250]: d + s
# Out[251]: 
#     A  B  C  D  E
# 0  11  8  6  1  3
# 1   5  5  9  8  3
# 2  10  3  6  8  6

# s の index を並べ替えたものを s2 とする
s_index = np.random.choice(s.index.values, replace=False, size=5)
s2 = s.reindex(s_index)

s2
# In [439]: s2
# Out[439]: 
# B    1
# E    2
# D    1
# C    4
# A    3

s + s2
# In [440]: s + s2
# Out[445]: 
# A    6
# B    2
# C    8
# D    2
# E    4

s2 + d
# In [446]: s2 + d
# Out[452]: 
#     A  B  C  D  E
# 0  11  8  6  1  3
# 1   5  5  9  8  3
# 2  10  3  6  8  6

s_data  = np.concatenate(([-100], s2.values, [100]))
s_index = np.concatenate((['X'] , s2.index , ['Y']))
s3 = pd.Series(s_data, s_index)

s3
# In [495]: s3
# Out[495]: 
# X   -100
# B      1
# E      2
# D      1
# C      4
# A      3
# Y    100

s + s3
# In [496]: s + s3
# Out[505]: 
# A    6.0
# B    2.0
# C    8.0
# D    2.0
# E    4.0
# X    NaN
# Y    NaN


d + s3
# In [506]: d + s3
# Out[514]: 
#     A  B  C  D  E   X   Y
# 0  11  8  6  1  3 NaN NaN
# 1   5  5  9  8  3 NaN NaN
# 2  10  3  6  8  6 NaN NaN

# 上記の ABCDE列 については計算されていることを確認
(d + s3).loc[:, list('ABCDE')] == d + s2
# In [590]: (d + s3).loc[:, list('ABCDE')] == d + s2
# Out[612]: 
#       A     B     C     D     E
# 0  True  True  True  True  True
# 1  True  True  True  True  True
# 2  True  True  True  True  True

#########################################################################
# A,B,C,D,E の列は共通のラベルなので NaN にはならないが、 X,Y は共通で
# はないので NaN になる。以下のように共通のラベルがない時は全て NaN に
# なる。
#########################################################################

s_data  = np.random.randint(0,5,5)
s_index = list('PQRST')
s4 = pd.Series(s_data, index=s_index)

s4
# In [581]: s4
# Out[583]: 
# P    4
# Q    0
# R    0
# S    0
# T    3

s + s4
# In [584]: s + s4
# Out[586]: 
# A   NaN
# B   NaN
# C   NaN
# D   NaN
# E   NaN
# P   NaN
# Q   NaN
# R   NaN
# S   NaN
# T   NaN

d + s4
# In [587]: d + s4
# Out[589]: 
#     A   B   C   D   E   P   Q   R   S   T
# 0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
# 1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
# 2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

# 行ラベルのみに Series の index と同じ ラベルがあった場合
d.T
# In [621]: d.T
# Out[621]: 
#    0  1  2
# A  8  2  7
# B  7  4  2
# C  2  5  2
# D  0  7  7
# E  1  1  4

d.T.index
# In [622]: d.T.index
# Out[631]: Index(['A', 'B', 'C', 'D', 'E'], dtype='object')

d.T.columns
# In [632]: d.T.columns
# Out[635]: RangeIndex(start=0, stop=3, step=1)

d + s
# In [649]: d + s
# Out[649]: 
#     A  B  C  D  E
# 0  11  8  6  1  3
# 1   5  5  9  8  3
# 2  10  3  6  8  6

d.T + s
# In [650]: d.T + s
# Out[652]: 
#     0   1   2   A   B   C   D   E
# A NaN NaN NaN NaN NaN NaN NaN NaN
# B NaN NaN NaN NaN NaN NaN NaN NaN
# C NaN NaN NaN NaN NaN NaN NaN NaN
# D NaN NaN NaN NaN NaN NaN NaN NaN
# E NaN NaN NaN NaN NaN NaN NaN NaN

# 上記よりデフォルトでは常に DataFrame の列ラベルに対してマッチングが行われることがわかる

####################################################################
# おまけ(1)
# DataFrame オブジェクト の columns ではなく、 index にマッチして計算
# して欲しい場合は、 メソッドを使用し引数に axis='index' を指定すると
# できる
####################################################################

d.T.add(s, axis='index')
# In [745]: d.T.add(s, axis='index')
# Out[746]: 
#     0  1   2
# A  11  5  10
# B   8  5   3
# C   6  9   6
# D   1  8   8
# E   3  3   6

d.T.add(s, axis='index').T == d + s
# In [776]: d.T.add(s, axis='index').T == d + s
# Out[781]: 
#       A     B     C     D     E
# 0  True  True  True  True  True
# 1  True  True  True  True  True
# 2  True  True  True  True  True

####################################################################
# おまけ(2)
# index などを無視して、何がなんでも足したい場合は、 values に対して計
# 算を行う(この場合 numpy の計算なので、 numpy broadcastng rule が適用
# 可能な形状にかぎる))
####################################################################

s4
# In [782]: s4
# Out[814]: 
# P    4
# Q    0
# R    0
# S    0
# T    3

d
# In [815]: d
# Out[823]: 
#    A  B  C  D  E
# 0  8  7  2  0  1
# 1  2  4  5  7  1
# 2  7  2  2  7  4

dd = d.copy()
dd.loc[:,:] = dd.values + s4.values
# In [931]: dd
# Out[931]: 
#     A  B  C  D  E
# 0  12  7  2  0  4
# 1   6  4  5  7  4
# 2  11  2  2  7  7

共通ラベルにない値は 「NaN + 値」 で 「NaN」になってしまうが、 「NaN + 値」を 「0 + 値」、 「NaN + NaN」 を 「0 + 0」 のように計算して欲しい場合は、以下のように fill_value 引数を使用する。(ただし、DataFrame と Series 同士の演算で fill_value を使用するとエラーになってしまったので注意)

s
# In [243]: s
# Out[243]: 
# A    3
# B    1
# C    4
# D    1
# E    2
# dtype: int64

s3
# In [495]: s3
# Out[495]: 
# X   -100
# B      1
# E      2
# D      1
# C      4
# A      3
# Y    100

s + s3
# In [496]: s + s3
# Out[505]: 
# A    6.0
# B    2.0
# C    8.0
# D    2.0
# E    4.0
# X    NaN
# Y    NaN

s.add(s3, fill_value=0)
# In [938]: s.add(s3, fill_value=0)
# Out[971]: 
# A      6.0
# B      2.0
# C      8.0
# D      2.0
# E      4.0
# X   -100.0
# Y    100.0


d + s3
# In [506]: d + s3
# Out[514]: 
#     A  B  C  D  E   X   Y
# 0  11  8  6  1  3 NaN NaN
# 1   5  5  9  8  3 NaN NaN
# 2  10  3  6  8  6 NaN NaN

d.add(s3, fill_value=0.0)
# NotImplementedError: fill_value 0.0 not supported.
# 上のようにDataFrame と Series で fill_value 引数を指定するとエラーになる
# パッと思いつたところでは以下のようにかけば実現できるのではないかと思います
d.reindex(columns=s3.index).fillna(0) + s3.fillna(0)

# ただこれだと、元々持っていた d の列が消えてしまうのでそれを保持するには
# 以下のようにするとできるのではないかと思います
dd = d.reindex(columns=d.columns|s3.index)
dd.loc[:, s3.index] = d.reindex(columns=s3.index).fillna(0) + s3.fillna(0)

以上です。おやすみなさい。