You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在apply()的自定义函数中使用dplyr::pull()按列名引用变量?

问题描述

我正在用apply()函数遍历数据框的每一行,对不同列执行特定操作,但希望能引用原始列名(而非基于索引子集)并保持变量的原始类型,偏好使用dplyr的语法。

背景与数据集

我有两个数据集:

  1. 广播电视广告投放数据,用于分析广告投放前10分钟的时段:
spot_DMA           spot_time         before_spot
    1 LOS ANGELES 2022-01-31 10:02:00 2022-01-31 09:52:00
    2    NEW YORK 2022-02-01 14:22:00 2022-02-01 14:12:00
    3     ATLANTA 2022-02-02 08:20:00 2022-02-02 08:10:00
    4      AUSTIN 2022-02-03 03:16:00 2022-02-03 03:06:00

生成代码:

spot.time <- c("2022-01-31 10:02:00", "2022-02-01 14:22:00", 
               "2022-02-02 08:20:00", "2022-02-03 03:16:00")

spot.DMA <- c("LOS ANGELES", "NEW YORK", "ATLANTA", "AUSTIN")

time_before <- 10

spot_df <- data.frame(spot_DMA = spot.DMA, spot_time = spot.time) %>% 
  mutate(spot_time = as.POSIXct(spot_time)) %>% 
  mutate(before_spot = spot_time - minutes(time_before)) # 计算广告投放前10分钟的时间点
  1. 网站流量数据,用于分析广告投放前后的会话数:
web_time     web_DMA web_sessions
    1  2022-01-31 09:55:00 LOS ANGELES            2
    2  2022-01-31 10:15:00 LOS ANGELES            5
    3  2022-01-31 10:18:00 LOS ANGELES            3
    4  2022-01-31 10:20:00 LOS ANGELES            5
    5  2022-01-31 10:24:00 LOS ANGELES            4
    6  2022-01-31 10:28:00 LOS ANGELES            4
    7  2022-02-02 08:11:00     ATLANTA            6
    8  2022-02-02 08:22:00     ATLANTA            7
    9  2022-02-02 08:23:00     ATLANTA            8
    10 2022-02-02 08:42:00     ATLANTA            4
    11 2022-02-02 08:43:00     ATLANTA            3
    12 2022-02-02 08:45:00     ATLANTA            1

生成代码:

web.time <- c("2022-01-31 09:55:00", "2022-01-31 10:15:00", 
              "2022-01-31 10:18:00", "2022-01-31 10:20:00",
              "2022-01-31 10:24:00", "2022-01-31 10:28:00",
              "2022-02-02 08:11:00", "2022-02-02 08:22:00", 
              "2022-02-02 08:23:00", "2022-02-02 08:42:00",
              "2022-02-02 08:43:00", "2022-02-02 08:45:00")

web.DMA <- c(rep("LOS ANGELES", 6), rep("ATLANTA", 6)) 

web.sessions <- c(2, 5, 3, 5, 4, 4,
                  6, 7, 8, 4, 3, 1)

web_df <- data.frame(web_time = web.time, web_DMA = web.DMA, web_sessions = web.sessions) %>% 
  mutate(web_time = as.POSIXct(web_time))

当前可行但不满意的实现(用列索引)

通过列索引提取值可以正常运行,但无法使用列名:

# 此代码可行,但依赖列索引
spot_time_function_subset <- function(df){
  
  spot_dma_extract <- df[[1]]
  
  spot_time_extract <- df[[2]]
  
  spot_before_extract <- df[[3]]
  
  
  web_before <- web_df %>% 
    # 筛选当前DMA的网站会话
    filter(web_DMA == spot_dma_extract) %>% 
    # 筛选广告投放前10分钟到投放时的会话
    filter(between(web_time, as.POSIXct(spot_before_extract), 
                   as.POSIXct(spot_time_extract))) %>% 
    # 求和会话数
    summarise(Total = sum(web_sessions)) %>% 
    pull(Total)
  
  
  return(web_before/time_before) # 计算每分钟平均会话数
  
}

web_session_avg <- apply(spot_df, 1, spot_time_function_subset)           
spot_df_bind <- bind_cols(spot_df, web_session_avg)

尝试的写法(无法与apply配合)

希望传递列名给函数,但apply()无法正常工作,因为函数会拿到整列而非单行值:

# 此语法无法与apply()配合
spot_time_function_pull <- function(df, spot_DMA, spot_time, before_spot){
  
  spot_dma_extract <- df %>% 
    pull({{spot_DMA}})
  
  spot_time_extract <- df %>% 
    pull({{spot_time}})
  
  spot_before_extract <- df %>% 
    pull({{before_spot}})
  
  # 此处会出错:上面三个变量是整列数据,而非单行值
  
  web_before <- web_df %>% 
    # 筛选当前DMA的网站会话
    filter(web_DMA == spot_dma_extract) %>% 
    # 筛选广告投放前10分钟到投放时的会话
    filter(between(web_time, as.POSIXct(spot_before_extract), 
                   as.POSIXct(spot_time_extract))) %>% 
    # 求和会话数
    summarise(Total = sum(web_sessions)) %>% 
    pull(Total)
  
  
  return(web_before/time_before) # 计算每分钟平均会话数
  
}

spot_time_function_pull(spot_df, spot_DMA, spot_time, before_spot)            

# apply无法正常运行
apply(spot_df, 1, spot_time_function_pull)

核心需求

希望用apply()遍历每行时,能引用原始列名并保持变量类型,偏好dplyr语法,是否可行?


解决方案

首先明确:apply()按行处理数据框时,会把每行转换成原子向量,丢失列名和原始类型(比如POSIXct时间会被转成字符),所以直接用apply()很难实现你要的效果。更推荐用dplyr或purrr的工具,既保留列名和类型,又符合tidyverse风格:

方案1:用dplyr::rowwise() + mutate()

这是最符合dplyr习惯的写法,直接按行处理,保留所有列的类型和名称:

library(dplyr)

spot_df_result <- spot_df %>%
  rowwise() %>%
  mutate(
    web_session_avg = {
      # 直接引用当前行的列名,无需额外转换
      web_df_sub <- web_df %>%
        filter(web_DMA == spot_DMA,
               between(web_time, before_spot, spot_time)) %>%
        summarise(total = sum(web_sessions, na.rm = TRUE)) %>%
        pull(total)
      web_df_sub / time_before
    }
  ) %>%
  ungroup() # 取消按行分组,避免后续操作受影响

print(spot_df_result)

方案2:用purrr::pmap()

pmap可以按行传递列作为参数,完美匹配你想要的函数写法:

library(purrr)
library(dplyr)

# 定义接收单行参数的函数,直接用列名作为参数名
calc_avg_sessions <- function(spot_DMA, spot_time, before_spot) {
  web_df %>%
    filter(web_DMA == spot_DMA,
           between(web_time, before_spot, spot_time)) %>%
    summarise(total = sum(web_sessions, na.rm = TRUE)) %>%
    pull(total) %>%
    `/`(time_before)
}

# 按行传递列参数,pmap_dbl确保返回数值向量
spot_df_result <- spot_df %>%
  mutate(web_session_avg = pmap_dbl(., calc_avg_sessions))

print(spot_df_result)

为什么你的apply()写法不行?

apply(spot_df, 1, fun)会把每行转成一个没有列名的向量,并且所有元素类型会被统一(比如POSIXct时间会变成字符)。当你把这个向量传给函数时,函数无法识别列名,还要手动转换类型,完全失去了用列名的意义。

如果一定要用apply()(不推荐),可以手动给行向量命名并转换类型,但步骤繁琐:

spot_time_function_apply <- function(row) {
  # 给行向量命名,匹配原数据框列名
  names(row) <- colnames(spot_df)
  # 手动转换类型
  spot_dma <- row["spot_DMA"]
  spot_time <- as.POSIXct(row["spot_time"])
  before_spot <- as.POSIXct(row["before_spot"])
  
  web_before <- web_df %>%
    filter(web_DMA == spot_dma,
           between(web_time, before_spot, spot_time)) %>%
    summarise(Total = sum(web_sessions)) %>%
    pull(Total)
  
  return(web_before/time_before)
}

web_session_avg <- apply(spot_df, 1, spot_time_function_apply)
spot_df_bind <- bind_cols(spot_df, web_session_avg)

显然,前两种方案更简洁,也符合tidyverse的设计理念,完全满足你用列名、保留类型的需求。


内容的提问来源于stack exchange,提问作者James

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 19:31:17