dataframe局部賦值
背景
問題描述
如下圖所示:(A)上圖表示某儀器隨開關開閉前后的變化曲線;(B)下圖表示開關閉狀態。現在的需求有三個:
- 不考慮開關狀態下超過指定閾值時的監測值統計特征
- 開關打開狀態(B=1,粉色區域)下超過指定閾值的監測值值局部信息統計
- 開關閉合(B=0,空白區域)狀態下超過指定閾值的監測值值局部信息統計

直觀上看,(2)和(3)的結果統計結果應該存在明顯的差異。但實際實現過程中,得到的結果卻是完全一致。甚是煩惱,通過定位,發生了一個神奇的代碼,如下:
temp_data[temp_data['time'].between(tmp_start_time, tmp_end_time)][
'sig'] = tmp_value - 1
"""
temp_data:為我們的儀器檢測值
start_time:開始時間
end_time:表示結束時間
tmp_value:為我們關注的一種狀態
這里tmp_value-1的目的是基于檢測值以更顯眼的方式區分目標狀態
"""
看上去沒啥問題,就是一個簡單的datafram賦值語句,接下來讓我們通過模擬數據進行問題定位。
問題定位
a=pd.DataFrame({'a':range(10),'b':range(2,12)})
#備份一份a
b=a.copy()
a[a['a'].between(2,6)]['b']=100
#比較賦值前后a的變換
np.allclose(a.values,b.values)#True
賦值前后,dataframe a居然完全一樣。
pandas官方解釋
示例:
訪問
dfmi = pd.DataFrame([list('abcd'),
list('efgh'),
list('ijkl'),
list('mnop')],
columns=pd.MultiIndex.from_product([['one', 'two'],
['first', 'second']]))
dfmi
Out[357]:
one two
first second first second
0 a b c d
1 e f g h
2 i j k l
3 m n o p
# 第一種訪問方式
dfmi['one']['second']
"""
0 b
1 f
2 j
3 n
Name: second, dtype: object
"""
#第二種訪問方式
dfmi.loc[:, ('one', 'second')]
"""
0 b
1 f
2 j
3 n
Name: (one, second), dtype: object
"""
這些都產生相同的結果,那么你應該使用哪個?了解這些操作的順序以及為什么方法 2 ( .loc) 比方法 1 (chained []) 更受青睞是很有啟發性的。
dfmi['one']選擇列的第一級并返回單索引的 DataFrame。然后另一個 Python 操作dfmi_with_one['second']選擇由 索引的系列'second'。這由變量表示,dfmi_with_one因為 pandas 將這些操作視為單獨的事件。例如,對 的單獨調用__getitem__,因此必須將它們視為線性操作,它們一個接一個地發生。
相比之下df.loc[:,('one','second')],將嵌套元組傳遞(slice(None),('one','second'))給單個調用 __getitem__. 這使得 pandas 可以將其作為一個整體來處理。此外,這種操作順序可以顯著加快,并且如果需要,可以對**兩個軸進行索引。
重新賦值
訪問時兩種方式只是性能問題,賦值時第二種方式回觸發SettingWithCopy警告是,并且原始數據對象并未發生小改。
#第一種
dfmi.loc[:, ('one', 'second')] = 100
# becomes
dfmi.loc.__setitem__((slice(None), ('one', 'second')), 100)
"""
one two
first second first second
0 a 100 c d
1 e 100 g h
2 i 100 k l
3 m 100 o p
"""
#第二種:
dfmi['one']['second'] = 1000
# becomes
dfmi.__getitem__('one').__setitem__('second', 1000)
"""
C:\Users\B_Hanan\AppData\Local\Temp\ipykernel_4124\431561245.py:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
dfmi['one']['second'] = 100
C:\Users\B_Hanan\AppData\Local\Temp\ipykernel_4124\431561245.py:3: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
dfmi.__getitem__('one').__setitem__('second', 100)
one two
first second first second
0 a 100 c d
1 e 100 g h
2 i 100 k l
3 m 100 o p
"""
如何理解?
其實核心是當前數據對象是副本還是視圖?在numpy中我們已經介紹了視圖和副本的區別,從這里我們可以直接看出鏈式查詢([]的方式)實際上返回的是副本(賦值不會改變原數據),而.loc返回的實際上是視圖(view,會發生數據的更改)。
問題解決
所以針對上述問題,我們應該用loc進行賦值。
temp_data.loc[temp_data['time'].between(tmp_start_time, tmp_end_time),
'sig'] = tmp_value - 1
最終效果圖:
- 打開狀態下存在超限情況
- 關閉狀態下無超限情況


浙公網安備 33010602011771號