我需要根据行索引值进行inplace
值替换。 替换值是切片(行和列(数据帧计算。
设置
In [1]: import pandas as pd
In [2]: cols = [0, 1, 'A0', 'A1', 'A2', 'A3', 'B0', 'B1', 'B2', 'B3']
In [3]: data = [['sum', 4531.0010, 0, 0, 0, 2, 0, 0, 0, 7],
...: ['', 4531.0010, 5, 6, 3, 0, 5, 4, 7, 0],
...: ['', 4531.0010, 1, 3, 9, 0, 2, 2, 3, 0],
...: ['sum', 5037.0022, 0, 0, 0, 8, 0, 0, 0, 5],
...: ['', 5037.0022, 2, 2, 3, 0, 1, 3, 9, 0],
...: ['', 5037.0022, 5, 4, 7, 0, 5, 6, 3, 0]]
In [4]: df = pd.DataFrame(data=data, columns=cols)
In [5]: df = df.set_index(list(df.columns[[0, 1]]))
In [6]: df
Out[6]:
A0 A1 A2 A3 B0 B1 B2 B3
0 1
sum 4531.0010 0 0 0 2 0 0 0 7
4531.0010 5 6 3 0 5 4 7 0
4531.0010 1 3 9 0 2 2 3 0
sum 5037.0022 0 0 0 8 0 0 0 5
5037.0022 2 2 3 0 1 3 9 0
5037.0022 5 4 7 0 5 6 3 0
如您所见,该行是多重索引的,index=1 是一个表示数据子集的数字。 在每个数据子集中,index=0 中都有一个"总和",我想将其向上(或向下("分布"到零余额。
计算基本上是将"A"列和具有相同值的索引 1 行相加为分母。 然后,该数据组的行的总和就是分子。 然后使用该比率在行之间分配总和。
对于行 = 4531.0010 和带 A 的列,其计算公式为:
(5+6+3(/(5+6+3+1+3+9(*2 = 第 1 行,A3 列(1+3+9(/(5+6+3+1+3+9(*2 = 第 2 行,A3
列
生成的df
如下所示:
Out[7]:
A0 A1 A2 A3 B0 B1 B2 B3
0 1
sum 4531.0010 0 0 0 2.000 0 0 0 7.000
4531.0010 5 6 3 1.037 5 4 7 4.870
4531.0010 1 3 9 0.923 2 2 3 2.130
sum 5037.0022 0 0 0 8.000 0 0 0 5.000
5037.0022 2 2 3 2.435 1 3 9 2.407
5037.0022 5 4 7 5.565 5 6 3 2.593
行数不是固定的 - 可能有一个,也可能有 10 行。
我尝试过什么
我尝试使用.pivot_table()
的变体,但我不知道如何使用除法来逆转该过程。 举个例子。
我也使用过.sum()
的变体,但试图使用切片来约束df
是我无法做到的。 众多例子之一。
我想我可以让它与很多 python 函数一起工作,但似乎应该更有效率。 任何方向都非常感谢。
解决方案工作正常,如果唯一的第一级MultiIndex
:
cols = [0, 1, 'A0', 'A1', 'A2', 'A3', 'B0', 'B1', 'B2', 'B3']
data = [['sum1', 4531.0010, 0, 0, 0, 2, 0, 0, 0, 7],
['sum1', 4531.0010, 5, 6, 3, 0, 5, 4, 7, 0],
['sum1', 4531.0010, 1, 3, 9, 0, 2, 2, 3, 0],
['sum2', 5037.0022, 0, 0, 0, 8, 0, 0, 0, 5],
['sum2', 5037.0022, 2, 2, 3, 0, 1, 3, 9, 0],
['sum2', 5037.0022, 5, 4, 7, 0, 5, 6, 3, 0]]
df = pd.DataFrame(data=data, columns=cols)
df = df.set_index(list(df.columns[[0, 1]]))
print (df)
A0 A1 A2 A3 B0 B1 B2 B3
0 1
sum1 4531.0010 0 0 0 2 0 0 0 7
4531.0010 5 6 3 0 5 4 7 0
4531.0010 1 3 9 0 2 2 3 0
sum2 5037.0022 0 0 0 8 0 0 0 5
5037.0022 2 2 3 0 1 3 9 0
5037.0022 5 4 7 0 5 6 3 0
#loop by first letters of values in columns
for c in df.columns.str[0].unique():
#filter values by first letter
df1 = df.filter(like=c)
#get sum per rows
s = df1.iloc[:, :-1].sum(axis=1)
#get last column
last_col = df1.iloc[:, -1]
#replace 0 in last column to previous non 0
last_col = last_col.mask(last_col == 0).ffill()
#divide by sum per first level with multiple by last_col
s = s.div(s.sum(level=0), level=0).mul(last_col)
#add to last column
df[last_col.name] += s
print (df)
A0 A1 A2 A3 B0 B1 B2 B3
0 1
sum1 4531.0010 0 0 0 2.000000 0 0 0 7.000000
4531.0010 5 6 3 1.037037 5 4 7 4.869565
4531.0010 1 3 9 0.962963 2 2 3 2.130435
sum2 5037.0022 0 0 0 8.000000 0 0 0 5.000000
5037.0022 2 2 3 2.434783 1 3 9 2.407407
5037.0022 5 4 7 5.565217 5 6 3 2.592593