【问题标题】:How to perform cell by cell comparison using pandas?如何使用熊猫进行逐个单元格的比较?
【发布时间】:2021-03-07 17:14:38
【问题描述】:

假设我有一个像下面这样的 df

col1   | type   | date_1 | date_2 | date_3 |.... | date_n
ab     |   A    |  -10   |        |  -10
ab     |   B    |  100   |   99   |  -12
cd     |   A    |   0    |  -25   |   6
cd     |   B    |  -1    |   8    |  -34
ab     |   A    |   98   |  -9    |   0
ab     |   B    |  -7    |  -2    |   0

第一步是删除包括0在内的所有正数

现在 df 应该是这样的,

col1   | type   | date_1 | date_2 | date_3 | .... | date_n
ab     |   A    |  -10   |        |  -10   |
ab     |   B    |        |        |  -12   |
cd     |   A    |        |  -25   |        |
cd     |   B    |  -1    |        |  -34   |
ab     |   A    |        |  -9    |        |
ab     |   B    |  -7    |  -2    |        |

第二步是根据“类型”A和B比较每个“日期”列的数字,

  • 如果 'type' A 行有一个负数,而 'type' B 为空白,则删除 'type' A 的 'date' col 的负数

  • 如果 'type' B 行有一个负数并且 'type' A 是空白的,那么什么都不做

  • 如果两种类型都为空,则什么也不做

在这一步之后,df应该是这样的,

col1   | type   | date_1 | date_2 | date_3 | .... | date_n
ab     |   A    |        |        |  -10   |
ab     |   B    |        |        |  -12   |
cd     |   A    |        |        |        |
cd     |   B    |  -1    |        |  -34   |
ab     |   A    |        |  -9    |        |
ab     |   B    |  -7    |  -2    |        |

最后一步,

  • 如果两种类型对当前都是负数,对于每组col1(ab,cd,ab),检查同一行的相同Ath和Bth的左侧值,

    1) If both types A and B values are blank, then remove the remove the negative number of current row 'type' A and keep the -ve number of 'type' B
    
    2) If either of the types are blank, then remove the negative of the current row 'type' B and keep the -ve number of 'type' A
    

最后,final_df 应该是这样的,

col1   | type   | date_1 | date_2 | date_3 | .... | date_n
ab     |   A    |        |        |        |
ab     |   B    |        |        |  -12   |
cd     |   A    |        |        |        |
cd     |   B    |  -1    |        |  -34   |
ab     |   A    |        |  -9    |        |
ab     |   B    |  -7    |        |        |

对于最后一步,比较应该从“date_2”开始。

解决此问题的最佳方法是什么?任何帮助将不胜感激!

注意:我不能使用列标题(日期的)来操作数据,因为它们会不断变化。

测试数据:

{'column1': ['CT', 'CT', 'NBB', 'NBB', 'CT', 'CT', 'NBB', 'NBB', 'HHH', 'HHH', 'TP1', 'TP1', 'TPR', 'TPR', 'PP1', 'PP1', 'PP1', 'PP1'], 'column2': ['POUPOU', 'POUPOU', 'PRPRP', 'PRPRP', 'STDD', 'STDD', 'STDD', 'STDD', 'STEVT', 'STEVT', 'SYSYS', 'SYSYS', 'SYSYS', 'SYSYS', 'SHW', 'SHW', 'JV', 'JV'], 'column3': ['V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV'], 'column4': ['A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B'], 'column5': [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan], 'column6': ['BBB', 'BBB', 'CCC', 'CCC', 'BBB', 'BBB', 'BBB', 'BBB', 'VVV', 'VVV', 'CHCH', 'CHCH', 'CHCH', 'CHCH', 'CCC', 'CCC', 'CHCH', 'CHCH'], 'column7': ['Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Mar-21', 'Mar-21', 'Mar-21', 'Mar-21', 'Mar-21', 'Mar-21', 'Apr-21', 'Apr-21', 'Mar-21', 'Mar-21'], 'Feb-21': [11655, 0, 0, 0, 121117, 0, 14948, 0, 0, 0, 0, 0, 0, 0, 1838, 0, 0, 0], 'Mar-21': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -16474.0, -16474.0, 7000.0, 7000.0, -19946.0, -19946.0, 16084.44444444444, 0.0, 0.0, 0.0], 'Apr-21': [104815.0, 104815.0, 17949.0, 17949.0, 96132.0, 96132.0, 0.0, 0.0, -17001.0, -33475.0, -878.0, 6122.0, 8398.0, -11548.0, -5297.073170731703, -5297.073170731703, -282.0, -282.0], 'May-21': [78260.0, 183075.0, 42557.0, 60506.0, -15265.0, 80867.0, -18.0, -18.0, 21084.0, -12391.0, -1831.0, 4291.0, 2862.0, -8686.0, 5261.25, -35.8231707317027, -369.0, -651.0], 'Jun-21': [-52480.0, 130595.0, -13258.0, 47248.0, -35577.0, 45290.0, 2434.0, 2416.0, 31147.0, 18756.0, -4310.0, -19.0, -4750.0, -13436.0, -92.0, -127.8231707317027, -280.0, -931.0], 'Jul-21': [-174544.0, -43949.0, -38127.0, 9121.0, -124986.0, -79696.0, -9707.0, -7291.0, 13577.0, 32333.0, 0.0, -19.0, -15746.0, -29182.0, 93.0, -34.8231707317027, -319.0, -1250.0], 'Aug-21': [35498.0, -8451.0, -37094.0, -27973.0, 79021.0, -675.0, -1423.0, -8714.0, 32168.0, 64501.0, 0.0, -19.0, 18702.0, -10480.0, 4347.634146341465, 4312.810975609762, -341.0, -1591.0], 'Sep-21': [44195.0, 35744.0, 2039.0, -25934.0, 70959.0, 70284.0, 2816.0, -5898.0, 38359.0, 102860.0, 0.0, -19.0, 18119.0, 7639.0, 5302.222222222219, 9615.033197831981, 0.0, -1591.0], 'Oct-21': [-13163.0, 22581.0, -4773.0, -30707.0, 205080.0, 275364.0, -709.0, -6607.0, -1397.0, 101463.0, 0.0, -19.0, 0.0, 7639.0, -34.0, 9581.033197831981, 0.0, -1591.0]}

预期输出:

{'column1': ['CT', 'CT', 'NBB', 'NBB', 'CT', 'CT', 'NBB', 'NBB', 'HHH', 'HHH', 'TP1', 'TP1', 'TPR', 'TPR', 'PP1', 'PP1', 'PP1', 'PP1'], 'column2': ['POUPOU', 'POUPOU', 'PRPRP', 'PRPRP', 'STDD', 'STDD', 'STDD', 'STDD', 'STEVT', 'STEVT', 'SYSYS', 'SYSYS', 'SYSYS', 'SYSYS', 'SHW', 'SHW', 'JV', 'JV'], 'column3': ['V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV', 'V', 'CV'], 'column4': ['A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B', 'A', 'B'], 'column5': [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan], 'column6': ['BBB', 'BBB', 'CCC', 'CCC', 'BBB', 'BBB', 'BBB', 'BBB', 'VVV', 'VVV', 'CHCH', 'CHCH', 'CHCH', 'CHCH', 'CCC', 'CCC', 'CHCH', 'CHCH'], 'column7': ['Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Apr-21', 'Mar-21', 'Mar-21', 'Mar-21', 'Mar-21', 'Mar-21', 'Mar-21', 'Apr-21', 'Apr-21', 'Mar-21', 'Mar-21'], 'Feb-21': [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan], 'Mar-21': [nan, nan, nan, nan, nan, nan, nan, nan, nan, -16474.0, nan, nan, nan, -19946.0, nan, nan, nan, nan], 'Apr-21': [nan, nan, nan, nan, nan, nan, nan, nan, -17001.0, nan, nan, nan, nan, -11548.0, nan, -5297.073170731703, nan, -282.0], 'May-21': [nan, nan, nan, nan, nan, nan, nan, -18.0, nan, -12391.0, nan, nan, nan, -8686.0, nan, -35.8231707317027, -369.0, nan], 'Jun-21': [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, -19.0, -4750.0, nan, -92.0, nan, -280.0, nan], 'Jul-21': [nan, -43949.0, nan, nan, nan, -79696.0, nan, -7291.0, nan, nan, nan, -19.0, -15746.0, nan, nan, -34.8231707317027, -319.0, nan], 'Aug-21': [nan, -8451.0, nan, -27973.0, nan, -675.0, -1423.0, nan, nan, nan, nan, -19.0, nan, -10480.0, nan, nan, -341.0, nan], 'Sep-21': [nan, nan, nan, -25934.0, nan, nan, nan, -5898.0, nan, nan, nan, -19.0, nan, nan, nan, nan, nan, -1591.0], 'Oct-21': [nan, nan, -4773.0, nan, nan, nan, -709.0, nan, nan, nan, nan, -19.0, nan, nan, nan, nan, nan, -1591.0]}

【问题讨论】:

  • 到目前为止你有没有尝试过?
  • 可以解释更多最后一步吗?为什么 ef 被删除 -2ab -10
  • check the left-hand-side of the same row 是什么意思?
  • 对于最后一步,我们从 col date_2 开始,并将值与 date_1 的值进行比较。对于ef,我们去掉-2,因为在左边,即col date_1中的值为负数,根据上面提到的条件,去掉对应类型B的值,保留类型A。希望这会有所帮助!
  • 根据最后一步下的第二点是正确的..

标签: python pandas


【解决方案1】:
df = pd.DataFrame(d)

rem_cols = [ 'col2', 'subtype',  'col3', 'col4', 'col5']
df['g'] = df.groupby(['col1', 'type'] ).cumcount()

df1 = df.drop(rem_cols, axis=1)

df1 = df1.set_index(['col1','g', 'type'])


df1.columns = pd.to_datetime(df1.columns)

first_date = df1.columns[0]
df1 = df1.unstack(-1)

# print (df1.stack(dropna=False).reset_index())

df1 = df1.mask(df1.ge(0))

m1 = (df1.xs('A', level=1, axis=1, drop_level=False).notna() & 
      df1.xs('B', level=1, axis=1, drop_level=False).rename(columns={'B':'A'}, level=1).isna())
m2 = (df1.xs('B', level=1, axis=1, drop_level=False).notna() &
      df1.xs('A', level=1, axis=1, drop_level=False).rename(columns={'A':'B'}, level=1).isna())

m = m1.join(m2)

df1 = df1.mask(m)
# print (df1)

df2 = df1.groupby(level=1, axis=1).shift(1, axis=1)
# print (df2)

mask1 = df1.notna() & df2.isna() & (df1.columns.get_level_values(1) == 'A')[ None, :]
#avoid change values for date_1
mask1[first_date] = False

mask2 = df1.notna() & df2.notna() & (df1.columns.get_level_values(1) == 'B')[ None, :]

df1 = df1.mask(mask1).mask(mask2).stack(dropna=False)
# print (df1)

df = df[rem_cols + ['col1','g', 'type']].join(df1, on=['col1','g', 'type'])
print (df)    

【讨论】:

  • @royalewithcheese - 请给我一些时间,我会更改正确工作的答案
  • @royalewithcheese - 处理数据并不容易,但我可以试试。
  • 为您的解决方案 - col1,输入
  • @royalewithcheese - 只是考虑不合并替代方案,正在努力。顺便说一句,有很多列没有 type, col1 和 datetimes 列?
  • @royalewithcheese - 当然;)
【解决方案2】:

使用numpy 和构建真值表的替代方法。第 3 步的逻辑是一次写入,从不读取。添加了另一个测试用例 gh

import io
import pandas as pd
import numpy as np
df = pd.read_csv(io.StringIO("""col1   | type   | date_1 | date_2 | date_3 
ab     |   A    |  -10   |    --    |  -10
ab     |   B    |  100   |   99   |  -12
cd     |   A    |   0    |  -25   |   6
cd     |   B    |  -1    |   8    |  -34
ef     |   A    |  -98   |  -9    |   0
ef     |   B    |  -7    |  -2    |   0
gh     |   A    |  -22   |  0    |   -3
gh     |   B    |  -75   |  0    |   -1

"""), sep="\s+\|\s+", engine="python").replace("--", "", regex=True)

# reshape dataframe so it's in better structure for steps
dfs = df.set_index(["col1","type"]).unstack(1)

# step 1,  identify all -ve values
a = dfs.replace("", 0).astype(int).lt(0).values
# step 2, keep where negative for type A & B,  note 2 relates to len(["A","B"])
ap = a.reshape((a.shape[0]*a.shape[1])//2, 2).all(axis=1)
a = np.repeat(ap,2).reshape(a.shape)

# step 3
# B are odd columns...
bcol = [bcol for bcol in range(a.shape[1]) if bcol%2==1]
# if A&B are both -ve for first date,  remove B value for other dates
# logic: a. A&B both -ve: a[:,[0,1]].all(axis=1),2)
#        b. logical and(not(A&B -ve), (B -ve))
a[:,bcol[1:]] = a[:,bcol[1:]]&np.repeat(~a[:,[0,1]].all(axis=1),2).reshape(len(dfs),2)

# rebuild df with truth array built for step 1&2&3
dfs.loc[:] = np.where(a, dfs, "")

# back to original shape...
df = dfs.stack().reset_index()

输出

  col1 type date_1 date_2 date_3
0   ab    A                  -10
1   ab    B                  -12
2   cd    A                     
3   cd    B                     
4   ef    A    -98     -9       
5   ef    B     -7              
6   gh    A    -22            -3
7   gh    B    -75              

空白和负A

  • 将步骤 1 替换为更复杂的二进制逻辑
# col A, blanks & negative
a = (((dfs.columns.get_level_values(1)=="A") 
 & dfs.replace("",0).astype(int).le(0)) |
# col B, only negative
((dfs.columns.get_level_values(1)=="B") 
 & dfs.replace("",0).astype(int).lt(0))).values

【讨论】:

  • 我已经对步骤3进行了广泛的解释,你可以看看,如果有什么不清楚的地方请告诉我。另外,我对问题进行了编辑。
  • 已更新 - 步骤 3 的逻辑有效,但可读性不强...
  • 如果在第 2 步中,如果出现 B 为负数但 A 为空白的情况,我想为 B 保留负数,我需要更改什么?
  • 我已经更新了答案 - 这是对第 1 步的修改
  • 感谢您的更新。当我使用测试数据(我已在问题中添加)运行您的代码时,在最后一步出现以下错误operands could not be broadcast together with shapes (12,8) (12,2)
猜你喜欢
  • 2018-11-16
  • 2021-08-17
  • 2020-05-09
  • 1970-01-01
  • 2019-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-23
相关资源
最近更新 更多