1

我在熊猫中有一个多级列,df索引appid如下:

year   |2016    2017    2018    2019    2016  2017   2018   2019
       |ttl     ttl     ttl     ttl     tta   tta    tta    tta
-----------------------------------------------------------------
appid  |
75787  |NaN     227.0   470.0   426.0   NaN   25.0   23.0   21.0
146306 |NaN     858.0   226.0   NaN     NaN   14.0   35.0   NaN
159479 |NaN     NaN     0.0     NaN     NaN   NaN    3.5    NaN
163618 |NaN     0.0     650.0   100.0   NaN   12.0   14.6   123.0
215968 |23.0    0.0     NaN     NaN     45.0  2.0    NaN    NaN

我想将其转换df为可以通过最新年份的条目进行排序。例如

Year   |P2Y      PY      LY    P2Y    PY    LY
       |ttl     ttl     ttl    tta    tta   tta

----------------------------------------------------
appid  |
75787  |227.0   470.0   426.0  25.0   23.0   21.0
146306 |NaN     858.0   226.0  NaN    14.0   35.0
159479 |NaN     NaN     0.0    NaN    NaN    3.5
163618 |0.0     650.0   100.0  12.0   14.6   123.0
215968 |NaN     23.0    0.0    NaN    45.0   2.0
4

2 回答 2

1

您可以尝试处理转置数据集并使用shift

df.T \
  .apply(lambda x: x.shift(len(x) - x.index.get_loc(x.last_valid_index()) - 1)) \
  .T \
  .dropna(how='all', axis='columns'))

解释

  1. 使用转置数据集.T

  2. NaN 在每列的末尾按指定数量的值移动每列

    1. apply在每列上使用

    2. 使用with查找最后一个非NaN值。有关此步骤的更多详细信息,请参阅Locate first and last non NaN values in a Pandas DataFrame last_valid_indexget_loc

    3. 计算步骤 2.3 和 的行移位数len(x)。还要减去 1,因为步骤 2.2 中的索引采用上面的行索引。

    4. 用于shift移动列

  3. 最终将数据集转回步骤 1,使用.T

  4. 使用and删除所有NaNdropnahow='all', axis='columns'


代码+插图

# Step 1
print(df.T)
#             75787   146306  159479  163618  215968
# year appid
# 2016 ttl       NaN     NaN     NaN     NaN    23.0
# 2017 ttl     227.0   858.0     NaN     0.0     0.0
# 2018 ttl     470.0   226.0     0.0   650.0     NaN
# 2019 ttl     426.0     NaN     NaN   100.0     NaN
# 2016 tta       NaN     NaN     NaN     NaN    45.0
# 2017 tta      25.0    14.0     NaN    12.0     2.0
# 2018 tta      23.0    35.0     3.5    14.6     NaN
# 2019 tta      21.0     NaN     NaN   123.0     NaN


# Step 2.2.1
print(df.T.apply(lambda x: x.last_valid_index()))
# 75787     (2019, tta)
# 146306    (2018, tta)
# 159479    (2018, tta)
# 163618    (2019, tta)
# 215968    (2017, tta)
# dtype: object


# Step 2.2.2
print(df.T.apply(lambda x: x.index.get_loc(x.last_valid_index())))
# 75787     7
# 146306    6
# 159479    6
# 163618    7
# 215968    5
# dtype: int64


# Step 2
print(df.T.apply(lambda x: x.shift(
    len(x) - x.index.get_loc(x.last_valid_index()) - 1)))
#             75787   146306  159479  163618  215968
# year appid
# 2016 ttl       NaN     NaN     NaN     NaN     NaN
# 2017 ttl     227.0     NaN     NaN     0.0     NaN
# 2018 ttl     470.0   858.0     NaN   650.0    23.0
# 2019 ttl     426.0   226.0     0.0   100.0     0.0
# 2016 tta       NaN     NaN     NaN     NaN     NaN
# 2017 tta      25.0     NaN     NaN    12.0     NaN
# 2018 tta      23.0    14.0     NaN    14.6    45.0
# 2019 tta      21.0    35.0     3.5   123.0     2.0


# Step 3
print(df.T.apply(lambda x: x.shift(
    len(x) - x.index.get_loc(x.last_valid_index()) - 1)).T)
# year   2016   2017   2018   2019 2016  2017  2018   2019
# appid   ttl    ttl    ttl    ttl  tta   tta   tta    tta
# 75787   NaN  227.0  470.0  426.0  NaN  25.0  23.0   21.0
# 146306  NaN    NaN  858.0  226.0  NaN   NaN  14.0   35.0
# 159479  NaN    NaN    NaN    0.0  NaN   NaN   NaN    3.5
# 163618  NaN    0.0  650.0  100.0  NaN  12.0  14.6  123.0
# 215968  NaN    NaN   23.0    0.0  NaN   NaN  45.0    2.0



# Step 4
print(df.T.apply(lambda x: x.shift(
    len(x) - x.index.get_loc(x.last_valid_index()) - 1)).T
    .dropna(how='all', axis='columns'))

# year     2017   2018   2019  2017  2018   2019
# appid     ttl    ttl    ttl   tta   tta    tta
# 75787   227.0  470.0  426.0  25.0  23.0   21.0
# 146306    NaN  858.0  226.0   NaN  14.0   35.0
# 159479    NaN    NaN    0.0   NaN   NaN    3.5
# 163618    0.0  650.0  100.0  12.0  14.6  123.0
# 215968    NaN   23.0    0.0   NaN  45.0    2.0
于 2020-05-04T09:37:28.663 回答
1

您可以先DataFrame.stack使用多年的列,然后使用justify,过滤最后 3 列,创建 DataFrame 并在必要时通过DataFrame.unstackwith重新调整列名称的顺序: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
于 2020-05-04T09:49:00.077 回答