Восстановление мультииндекса фрейма данных (как из строки, так и из столбца) после groupby

#python #pandas #dataframe

#python #pandas #фрейм данных

Вопрос:

У меня есть фрейм данных, который проиндексирован таким образом.

                                   Value              Size
                           A               B      Market Cap
2019-07-01 AAPL         89.583458      9.328360  2.116356e 06
           AMGN         49.828466     10.058943  1.395518e 05
2019-10-01 AAPL         74.297570     11.237253  2.116356e 06
           AMGN         56.841946     10.237481  1.395518e 05
2019-12-31 AAPL         97.435257     14.736749  2.116356e 06
           AMGN         71.400903     12.859612  1.395518e 05
 

Я хочу применить функцию к каждому из его столбцов для каждой даты (так что 89.583458 и 49.828466 идут вместе, 9.328360 и 10.058943 идут вместе и так далее)

 winsorized_df = pipeline_df.groupby(level=0, axis=0).apply(
                lambda level_0_col: level_0_col.groupby(level=1, axis=1).apply(
                    lambda series: mstats.winsorize(a=series, limits=winsorize_bounds))
            )
 

Это дает мне

                                               Market Cap  ...                             B
2019-07-01  [[139551.76568603513], [139551.76568603513]]  ...  [[49.828465616227064], [49.828465616227064]]
2019-10-01  [[139551.76568603513], [139551.76568603513]]  ...    [[56.84194615992103], [56.84194615992103]]
2019-12-31  [[139551.76568603513], [139551.76568603513]]  ...    [[71.40090272484755], [71.40090272484755]]
 

Но теперь мне нужно восстановить потерянные индексы (чтобы вернуть ту же структуру, что и исходная), но не удалось установить as_index=False , разархивировать или использовать pd.MultiIndex.from_frame. Есть идеи? Возможно, есть лучший способ получить именно это из groupby вызова?

Ответ №1:

Проблема в том, что winsorize возвращает массив numpy. Итак, вы заменяете фрейм данных массивом numpy (именно поэтому вы видите [[...]] в своем выводе). Вместо этого вы должны заменить значения фрейма данных. Вот пример:

 import pandas
from scipy.stats.mstats import winsorize

# Recreating your dataframe
data = [
    {"date": "2019-07-01", "group": "AAPL", "A": 89.583458, "B": 9.328360, "Market Cap": 2.116356e 06},
    {"date": "2019-07-01", "group": "AMGN", "A": 49.828466, "B": 10.058943, "Market Cap": 1.395518e 05},
    {"date": "2019-10-01", "group": "AAPL", "A": 74.297570, "B": 11.237253, "Market Cap": 2.116356e 06},
    {"date": "2019-10-01", "group": "AMGN", "A": 56.841946, "B": 10.237481, "Market Cap": 1.395518e 05},
    {"date": "2019-12-31", "group": "AAPL", "A": 97.435257, "B": 14.736749, "Market Cap": 2.116356e 06},
    {"date": "2019-12-31", "group": "AMGN", "A": 71.400903, "B": 12.859612, "Market Cap": 1.395518e 05},
]
index = [
    [pandas.to_datetime(line.get("date")) for line in data],
    [line.get("group") for line in data],
]
columns = [
    ["Value", "Value", "Size"],
    ["A", "B", "Market Cap"]
]
df = pandas.DataFrame(data=[[line.get("A"), line.get("B"), line.get("Market Cap")] for line in data], index=index, columns=columns)


# Your lambda function in a separate definition
def process_group(group):

    # Nested
    def _sub(sub):
        # winsorize returns an numpy array, sub is a dataframe; sub[:] replaces the "values" of the dataframe, not the dataframe itself
        sub[:] = winsorize(a=sub, limits=[0.4, 0.6])  # I didn't know your limits so I've guessed...
        return sub

    # Return the result of the processing on the nested group
    return group.groupby(level=1, axis=1).apply(_sub)

# Process the groups
df = df.groupby(level=0, axis=0).apply(process_group)
 

Вывод:

                      Value                  Size
                         A          B Market Cap
2019-07-01 AAPL  49.828466   9.328360   139551.8
           AMGN  49.828466   9.328360   139551.8
2019-10-01 AAPL  56.841946  10.237481   139551.8
           AMGN  56.841946  10.237481   139551.8
2019-12-31 AAPL  71.400903  12.859612   139551.8
           AMGN  71.400903  12.859612   139551.8
 

Комментарии:

1. Это красивое, элегантное решение. Намного проще отлаживать поведение. Я буду использовать этот шаблон при применении / отображении функций. Слава! 🙂