Нормализуйте значения столбцов по среднемесячным значениям с добавленным измерением группы - PullRequest
0 голосов
/ 21 января 2020

Начальное примечание

Я уже запустил это, но выполнение занимает очень много времени. Мой DataFrame составляет около 500 МБ. Я надеюсь услышать некоторые отзывы о том, как выполнить это как можно быстрее.

Постановка проблемы

Я хочу нормализовать столбцы DataFrame на mean значений столбца в течение каждого месяца , Дополнительная сложность заключается в том, что у меня есть столбец с именем group, который обозначает другой датчик, в котором был измерен параметр (столбец). Поэтому анализ должен повторяться около group и каждый месяц.

Пример DF

                     X  Y  Z  group 
2019-02-01 09:30:07  1  2  1  'grp1'
2019-02-01 09:30:23  2  4  3  'grp2'
2019-02-01 09:30:38  3  6  5  'grp1'
                ...

Код (функциональный, но медленный)

Это код, который Я использовал. Кодовые аннотации содержат описания большинства строк. Я понимаю, что три цикла for вызывают эту проблему во время выполнения, но у меня нет предвидения, чтобы обойти это. Кто-нибудь знает какие-либо

    # Get mean monthly values for each group
    mean_per_month_unit = process_df.groupby('group').resample('M', how='mean')
    # Store the monthly dates created in last line into a list called month_dates
    month_dates = mean_per_month_unit.index.get_level_values(1)
    # Place date on multiIndex columns. future note: use df[DATE, COL_NAME][UNIT] to access mean value
    mean_per_month_unit = mean_per_month_unit.unstack().swaplevel(0,1,1).sort_index(axis=1)

    divide_df = pd.DataFrame().reindex_like(df)
    process_cols.remove('group')
    for grp in group_list:
        print(grp)
        # Iterate through month
        for mnth in month_dates:
            # Make mask where month and group
            mask = (df.index.month == mnth.month) & (df['group'] == grp)
            for col in process_cols:
                # Set values of divide_df 
                divide_df.iloc[mask.tolist(), divide_df.columns.get_loc(col)] = mean_per_month_unit[mnth, col][grp]
    # Divide process_df with divide_df
    final_df = process_df / divide_df.values

РЕДАКТИРОВАТЬ: Пример данных

Вот данные в формате CSV.

EDIT2: Текущий код (в соответствии с текущий ответ)

def normalize_df(df):

    df['month'] = df.index.month
    print(df['month'])
    df['year'] = df.index.year
    print(df['year'])

    def find_norm(x, df_col_list): # x is a row in dataframe, col_list is the list of columns to normalize
        agg = df.groupby(by=['group', 'month', 'year'], as_index=True).mean()
        print("###################", x.name, x['month'])
        for column in df_col_list: # iterate over col list, find mean from aggregations, and divide the value by
            print(column)
            mean_col = agg.loc[(x['group'], x['month'], x['year']), column]
            print(mean_col)
            col_name = "norm" + str(column)
            x[col_name] = x[column] / mean_col # norm

        return x

    normalize_cols = df.columns.tolist()
    normalize_cols.remove('group')
    #normalize_cols.remove('mode')
    df2 = df.apply(find_norm, df_col_list = normalize_cols, axis=1)

Код отлично работает в течение одной итерации, а затем завершается с ошибкой:

KeyError: ('month', 'occurred at index 2019-02-01 11:30:17')

Как я уже сказал, он работает правильно один раз. Тем не менее, он повторяется по той же строке снова, а затем не удается. Согласно документации df.apply () я вижу, что первая строка всегда выполняется дважды. Я просто не уверен, почему это не удается во второй раз.

1 Ответ

1 голос
/ 21 января 2020

Предполагая, что необходимо сгруппировать столбцы по mean и month, существует другой подход:

  1. Создание новых столбцов - месяца и года из индекса. Для этого можно использовать df.index.month при условии, что индекс имеет тип DatetimeIndex
    type(df.index) # df is the original dataframe
    #pandas.core.indexes.datetimes.DatetimeIndex

    df['month'] = df.index.month
    df['year'] = df.index.year # added year assuming the grouping occurs per grp per month per year. No need to add this column if year is not to be considered.
Теперь сгруппируйте по (grp, month, year) и объедините, чтобы найти среднее значение для каждого столбца. (Добавлен год при условии, что группировка происходит по группам в месяц в год. Нет необходимости добавлять этот столбец, если год не рассматривается.)
    agg = df.groupby(by=['grp', 'month', 'year'], as_index=True).mean()
Используйте функцию для вычисления нормализованных значений и используйте apply() поверх исходного кадра данных
def find_norm(x, df_col_list): # x is a row in dataframe, col_list is the list of columns to normalize

    for column in df_col_list: # iterate over col list, find mean from aggregations, and divide the value by the mean. 
        mean_col = agg.loc[(str(x['grp']), x['month'], x['year']), column]
        col_name = "norm" + str(column)
        x[col_name] = x[column] / mean_col # norm

    return x

df2 = df.apply(find_norm, df_col_list = ['A','B','C'], axis=1)
#df2 will now have 3 additional columns - normA, normB, normC 
df2:

                        A   B   C   grp month year  normA     normB     normC
2019-02-01 09:30:07     1   2   3   1   2   2019    0.666667    0.8     1.5
2019-03-02 09:30:07     2   3   4   1   3   2019    1.000000    1.0     1.0
2019-02-01 09:40:07     2   3   1   2   2   2019    1.000000    1.0     1.0
2019-02-01 09:38:07     2   3   1   1   2   2019    1.333333    1.2     0.5

В качестве альтернативы, для шага 3 можно join agg и df фреймы данных и найти норм. Надеюсь, это поможет!

Вот как будет выглядеть код:


# Step 1
df['month'] = df.index.month
df['year'] = df.index.year # added year assuming the grouping occurs 

# Step 2
agg = df.groupby(by=['grp', 'month', 'year'], as_index=True).mean()

# Step 3
def find_norm(x, df_col_list): # x is a row in dataframe, col_list is the list of columns to normalize

    for column in df_col_list: # iterate over col list, find mean from aggregations, and divide the value by the mean. 
        mean_col = agg.loc[(str(x['grp']), x['month'], x['year']), column]
        col_name = "norm" + str(column)
        x[col_name] = x[column] / mean_col # norm

    return x

df2 = df.apply(find_norm, df_col_list = ['A','B','C'], axis=1)
...