【问题标题】:Pandas DataFrame: Fill NA values based on group meanPandas DataFrame:根据组平均值填充 NA 值
【发布时间】:2021-05-12 03:16:46
【问题描述】:

我想用 groupby 对象中的值更新 Pandas DataFrame 列的 NA 值。

我们用一个例子来说明:

我们有以下 DataFrame 列:

|--------|-------|-----|-------------|
| row_id | Month | Day | Temperature |
|--------|-------|-----|-------------|
| 1      | 1     | 1   | 14.3        |
| 2      | 1     | 1   | 14.8        |
| 3      | 1     | 2   | 13.1        |
|--------|-------|-----|-------------|

我们只是每天多次测量温度,持续数月。现在,让我们假设对于我们的一些记录,温度读数失败并且我们有一个NA

|--------|-------|-----|-------------|
| row_id | Month | Day | Temperature |
|--------|-------|-----|-------------|
| 1      | 1     | 1   | 14.3        |
| 2      | 1     | 1   | 14.8        |
| 3      | 1     | 2   | 13.1        |
| 4      | 1     | 2   | NA          |
| 5      | 1     | 3   | 14.8        |
| 6      | 1     | 4   | NA          |
|--------|-------|-----|-------------|

我们可以只使用 panda 的 .fillna(),但是我们想要更复杂一点。由于每天有多个读数(每天可能有 100 个),我们想取每日平均值并将其用作我们的填充值。

我们可以通过简单的 groupby 获得每日平均值:

avg_temp_by_month_day = df.groupby(['month'])['day'].mean()

这为我们提供了按月计算每一天的方法。问题是,如何最好地用 groupby 值填充 NA 值?

我们可以使用apply()

df['temperature'] = df.apply(
    lambda row: avg_temp_by_month_day.loc[r['month'], r['day']] if pd.isna(r['temperature']) else r['temperature'], 
    axis=1
)

但这确实很慢(超过 100 万条记录)。

是否有矢量化方法,可能使用np.where(),或者可能创建另一个系列并合并。

执行此操作的更有效方法是什么?

谢谢!

【问题讨论】:

  • 我也在考虑使用 df.update() - 但我还不确定如何对齐 groupby 对象。

标签: python pandas dataframe


【解决方案1】:

我不确定这是否是最快的,但是apply 需要大约 1 小时,而 +1M 记录需要大约 20 秒。下面的代码已更新为适用于 1 列或多列。

local_avg_cols = ['temperature'] # can work with multiple columns

# Create groupby's to get local averages
local_averages = df.groupby(['month', 'day'])[local_avg_cols].mean()

# Convert to DataFrame and prepare for merge
local_averages = pd.DataFrame(local_averages, columns=local_avg_cols).reset_index()

# Merge into original dataframe
df = df.merge(local_averages, on=['month', 'day'], how='left', suffixes=('', '_avg'))

# Now overwrite na values with values from new '_avg' col
for col in local_avg_cols:
    df[col] = df[col].mask(df[col].isna(), df[col+'_avg'])
    
# Drop new avg cols
df = df.drop(columns=[col+'_avg' for col in local_avg_cols])

如果有人找到更有效的方法来执行此操作(在处理时间上或在可读性上有效),我将取消标记此答案并标记您的答案。谢谢!

【讨论】:

    【解决方案2】:

    我猜是什么加快了你的进程是两件事。首先,您不需要将 groupby 转换为数据框。其次,您不需要 for 循环。

    from pandas import DataFrame
    from numpy import nan
    
    # Populating the dataset
    df = {"Month": [1] * 6,
          "Day": [1, 1, 2, 2, 3, 4],
          "Temperature": [14.3, 14.8, 13.1, nan, 14.8, nan]}
    
    # Creating the dataframe
    df = pd.DataFrame(df, columns=df.keys())
    local_averages = df.groupby(['Month', 'Day'])['Temperature'].mean()
    df = df.merge(local_averages, on=['Month', 'Day'], how='left', suffixes=('', '_avg'))
    # Filling the missing values of the Temperature column with what is available in Temperature_avg
    df.Temperature.fillna(df.Temperature_avg, inplace=True)
    df.drop(columns="Temperature_avg", inplace=True)
    

    Groupby 是一个资源繁重的进程,因此请在使用时充分利用它。此外,正如您已经知道的那样,对于数据帧,循环并不是一个好主意。此外,如果您有大量数据,您可能希望避免从中创建额外的变量。如果我的数据有 1m 行和很多列,我可以将 groupby 放入合并中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-31
      • 2020-06-08
      • 1970-01-01
      • 2017-12-05
      • 1970-01-01
      • 1970-01-01
      • 2020-09-21
      • 2022-08-18
      相关资源
      最近更新 更多