Средневзвешенное значение датафреймов с маской по NaN - PullRequest
3 голосов
/ 03 марта 2020

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

РЕДАКТИРОВАТЬ: мне нужно усреднить больше, чем только два кадра данных, однако приведенный ниже пример кода включает только два из них.

import pandas as pd
import numpy as np

df1 = pd.DataFrame([[np.nan, 2, np.nan, 0],
                    [3, 4, np.nan, 1],
                    [np.nan, np.nan, np.nan, 5],
                    [np.nan, 3, np.nan, 4]],
                   columns=list('ABCD'))

df2 = pd.DataFrame([[3, 1, np.nan, 1],
                    [2, 5, np.nan, 3],
                    [np.nan, 4, np.nan, 2],
                    [np.nan, 2, 1, 5]],
                   columns=list('ABCD'))

Что я делаю:

  • преобразует каждый фрейм данных в массив массивов (строк), помещает все так преобразованные фреймы данных в массив:
def fromDfToArraysStack(df):

    for i in range(len(df)):
         arrayRow = df.iloc[i].values

         if i == 0:
             arraysStack = arrayRow
         else:
             arraysStack = np.vstack((arraysStack, arrayRow))

    return arraysStack

arraysStack1 = fromDfToArraysStack(df1)
arraysStack2 = fromDfToArraysStack(df2)
arrayOfArrays = np.array([arraysStack1, arraysStack2])
  • применить маску к nans и взять среднее значение:
masked = np.ma.masked_array(arrayOfArrays,
                            np.isnan(arrayOfArrays))
arrayAve = np.ma.average(masked,
                         axis = 0,
                         weights = [1,2])
  • преобразовать обратно в фрейм данных, возвращая nans обратно:
pd.DataFrame(np.row_stack(arrayAve.filled(np.nan)))

    0           1           2   3
0   3.000000    1.333333    NaN 0.666667
1   2.333333    4.666667    NaN 2.333333
2   NaN         4.000000    NaN 3.000000
3   NaN         2.333333    1.0 4.666667

Как я уже сказал, это работает, но, надеюсь, есть более лаконичный способ сделать это, кто-нибудь из одной строки?

Ответы [ 2 ]

1 голос
/ 03 марта 2020

Чтобы сделать его аккуратным в одну строку, я немного обманул с импортом, но вот лучшее, что я мог сделать:

import pandas as pd
import numpy as np
from numpy.ma import average as avg
from numpy.ma import masked_array as ma

df1 = pd.DataFrame([[np.nan, 2, np.nan, 0],
                    [3, 4, np.nan, 1],
                    [np.nan, np.nan, np.nan, 5],
                    [np.nan, 3, np.nan, 4]],
                   columns=list('ABCD'))

df2 = pd.DataFrame([[3, 1, np.nan, 1],
                    [2, 5, np.nan, 3],
                    [np.nan, 4, np.nan, 2],
                    [np.nan, 2, 1, 5]],
                   columns=list('ABCD'))

df1.combine(df2, lambda x, y: avg([ma(x, np.isnan(x)), ma(y, np.isnan(y))], 0, [1, 2]))

РЕДАКТИРОВАТЬ:

import pandas as pd
import numpy as np
from numpy.ma import average as avg
from numpy.ma import masked_array as ma

df1 = pd.DataFrame([[np.nan, 2, np.nan, 0],
                    [3, 4, np.nan, 1],
                    [np.nan, np.nan, np.nan, 5],
                    [np.nan, 3, np.nan, 4]],
                   columns=list('ABCD'))

df2 = pd.DataFrame([[3, 1, np.nan, 1],
                    [2, 5, np.nan, 3],
                    [np.nan, 4, np.nan, 2],
                    [np.nan, 2, 1, 5]],
                   columns=list('ABCD'))

def df_average(dfs, wgts):
      return pd.DataFrame(avg([ma(df.values, np.isnan(df.values)) for df in dfs], 0, wgts))


df_average(dfs=[df1, df2], wgts=[1, 2])
1 голос
/ 03 марта 2020

Будет ли это работать для вас? Это не один вкладыш, но все же намного короче :)

import pandas as pd
import numpy as np

df3 = pd.DataFrame([[np.nan, 2, np.nan, 0],
[3, 4, np.nan, 1],
[np.nan, np.nan, np.nan, 5],
[np.nan, 3, np.nan, 4]],
columns=list('ABCD'))

df4 = pd.DataFrame([[3, 1, np.nan, 1],
[2, 5, np.nan, 3],
[np.nan, 4, np.nan, 2],
[np.nan, 2, 1, 5]],
columns=list('ABCD'))

weights = [1,2]
average = (df3*weights[0]+df4*weights[1])/sum(weights)
average[df3.isna()] = df4
average[df4.isna()] = df3
average

РЕДАКТИРОВАТЬ: Поскольку отметил, что скорость имеет значение, я приведу ниже оптимизированную версию и некоторые результаты производительности. В оптимизированной версии я преобразую кадры данных в numpy массивы, поскольку там он работает быстрее (как у вас в вашем примере):

import pandas as pd
import numpy as np
df3 = pd.DataFrame([[np.nan, 2, np.nan, 0],
[3, 4, np.nan, 1],
[np.nan, np.nan, np.nan, 5],
[np.nan, 3, np.nan, 4]],
columns=list('ABCD'))

df4 = pd.DataFrame([[3, 1, np.nan, 1],
[2, 5, np.nan, 3],
[np.nan, 4, np.nan, 2],
[np.nan, 2, 1, 5]],
columns=list('ABCD'))

weights = np.array([1,2])
df3 = df3.values
df4 = df4.values

average = (df3*weights[0]+df4*weights[1])/np.sum(weights)
np.copyto(average,df4,where=np.isnan(df3))
np.copyto(average,df3,where=np.isnan(df4))
average

Результаты синхронизации:

  • Ваше: 1.18 ms ± 27.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
  • Мой новый: 18.4 µs ± 1.45 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
  • Моя старая версия была хуже вашей примерно на 8,5 мс.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...