Организовать фрейм данных на основе наличия данных в столбцах в многоуровневом фрейме данных - PullRequest
1 голос
/ 04 мая 2020

У меня есть многоуровневые столбцы в pandas 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

Ответы [ 2 ]

1 голос
/ 04 мая 2020

Вы можете сначала 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
1 голос
/ 04 мая 2020

Вы можете попробовать работать с транспонированным набором данных и использовать 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. Найдите последние значения не NaN, используя last_valid_index с get_loc. Подробнее об этом шаге см. Здесь Найдите первое и последнее значения, отличные от NaN, в Pandas DataFrame

    3. Вычислите число сдвигов строк из шага 2.3 и len(x). Также вычтите 1, так как для индекса из шага 2.2 используется индекс строки, приведенный выше.

    4. Используйте shift, чтобы сместить столбец

  3. В конечном итоге транспонировать набор данных обратно, как в шаге 1, используя .T

  4. Удалить все столбцы NaN, используя dropna и how='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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...