Вы можете сначала DataFrame.stack
по годам для столбцов, затем использовать justify
, отфильтровать последние 3 столбца, создать DataFrame и изменить форму на DataFrame.unstack
с помощью DataFrame.reindex
для изменения порядка имен столбцов при необходимости:
df1 = df.stack()
arr = justify(df1.to_numpy(),invalid_val=np.nan, side='right')[:, -3:]
print (arr)
[[ 25. 23. 21. ]
[227. 470. 426. ]
[ nan 14. 35. ]
[ nan 858. 226. ]
[ nan nan 3.5]
[ nan nan 0. ]
[ 12. 14.6 123. ]
[ 0. 650. 100. ]
[ nan 45. 2. ]
[ nan 23. 0. ]]
mux = pd.MultiIndex.from_product([df.columns.levels[1], ['P2Y','PY','LY']])
df2 = (pd.DataFrame(arr, index=df1.index, columns=['P2Y','PY','LY'])
.unstack()
.swaplevel(1,0, axis=1)
.reindex(mux, axis=1))
print (df2)
tta ttl
P2Y PY LY P2Y PY LY
75787 25.0 23.0 21.0 227.0 470.0 426.0
146306 NaN 14.0 35.0 NaN 858.0 226.0
159479 NaN NaN 3.5 NaN NaN 0.0
163618 12.0 14.6 123.0 0.0 650.0 100.0
215968 NaN 45.0 2.0 NaN 23.0 0.0
Функция:
#https://stackoverflow.com/a/44559180/2901002
def justify(a, invalid_val=0, axis=1, side='left'):
"""
Justifies a 2D array
Parameters
----------
A : ndarray
Input array to be justified
axis : int
Axis along which justification is to be made
side : str
Direction of justification. It could be 'left', 'right', 'up', 'down'
It should be 'left' or 'right' for axis=1 and 'up' or 'down' for axis=0.
"""
if invalid_val is np.nan:
mask = ~np.isnan(a)
else:
mask = a!=invalid_val
justified_mask = np.sort(mask,axis=axis)
if (side=='up') | (side=='left'):
justified_mask = np.flip(justified_mask,axis=axis)
out = np.full(a.shape, invalid_val)
if axis==1:
out[justified_mask] = a[mask]
else:
out.T[justified_mask.T] = a.T[mask.T]
return out