Pandas - Remplacer la valeur de ligne du calcul dérivé
J'ai besoin de faire un inplace
remplacement de valeur basé sur une valeur d'index de ligne. La valeur de remplacement est un calcul de trame de données (ligne et colonne) en tranches.
Installer
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
Comme vous pouvez le voir, la ligne est multiindexée avec index = 1 est un nombre qui représente un sous-ensemble des données. Dans chaque sous-ensemble de données, il y a une "somme" dans index = 0 que je voudrais "distribuer" vers le haut (ou vers le bas) aux soldes nuls.
Le calcul se compose essentiellement des colonnes "A" et des lignes d'index 1 avec la même valeur à additionner à un dénominateur. Ensuite, la somme de la ligne pour ce groupe de données est le numérateur. Le rapport est ensuite utilisé pour répartir la somme entre les lignes.
Pour les lignes = 4531,0010 et les colonnes avec un A, il serait calculé comme suit:
(5 + 6 + 3) / (5 + 6 + 3 + 1 + 3 + 9) * 2 = ligne 1, colonne A3
(1 + 3 + 9) / (5 + 6 + 3 + 1 + 3 + 9) * 2 = ligne 2, colonne A3
Le résultat df
ressemblerait à ceci:
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
Le nombre de lignes n'est pas fixe - il peut y en avoir une ou 10.
Ce que j'ai essayé
J'ai essayé d'utiliser des variantes de .pivot_table()
mais je n'arrive pas à comprendre comment inverser le processus en utilisant la division. A titre d' exemple .
J'ai également utilisé des variantes de .sum()
mais essayer de contraindre l' df
utilisation de tranches m'échappe. Un des nombreux exemples .
Je pense que je peux faire fonctionner cela avec beaucoup de fonctions python, mais il semble que cela devrait être possible plus efficacement. Toute direction est grandement appréciée.
Solution fonctionnant, si premiers niveaux uniques de 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