专栏名称: 走天涯徐小洋地理数据科学
一个爱生活的地理土博,分享GIS、遥感、空间分析、R语言、景观生态等地理数据科学实操教程、经典文献、数据资源
目录
相关文章推荐
51好读  ›  专栏  ›  走天涯徐小洋地理数据科学

告别无效清洗!用tidyverse分组过滤异常值,3行代码提升数据质量

走天涯徐小洋地理数据科学  · 公众号  ·  · 2025-02-12 17:05

正文

不要说我标题党哈,题目是DeepSeek起的!

有一位学员提问,如何按三西格玛法则处理观测异常值?

提问过程截图如下,放在这给大家参考学习,主要是让大家学会捋清思路,学员的提问很零碎,但是我从破碎的提问当中抽丝剥茧,把主要问题提了出来,并且转换为了计算机可以处理的内容。

<<< 左右滑动见更多 >>>

有一些观测数据如下所示:

观测数据
观测数据
  • 任务要求
    • 提取每个字段的均值
    • 判断字段中数值是否在均值±3倍标准差范围内,在保留,不在整行删除
    • 每个字段执行上面的判断
    • 按Location字段进行分组
    • 对LA开始一直到最后一个字段按三西格玛法则进行筛选

如何使用DeepSeek帮助写代码

对于比较复杂的工作,让人工智能大模型辅助写代码时,我认为有两个要点要注意:

  1. 问题描述要详尽
  2. 复杂问题尽量拆分

所以我在跟DeepSeek交流的时候,我没有直接让DeepSeek完成所有的工作,我是先让DeepSeek完成三西格玛法则的核心部分编写,分组部分后面再说。

后面就是具体的交流过程了,非常长,前面主要是思路讲解,核心内容完。

后面分组就是修改代码了,多聊聊就可以,不再粘贴进来了,太长了。


我给DeepSeek的提示词

假设你是一名R语言专家,现在有一个名为leafdata_A的数据框,有22个字段,"Location" "TreeID"   "LA"       "LBA"      "PA"       "LP"       "LBP"      "LBL"      "PL"       "LBW"      "PW"       "PLWR"     "LBLWR"
"NS"       "ASH"      "ASW"      "LBR...17" "LBSF"     "LBSP"     "LBR...20" "LBS"      "Env"  ,从第3个字段”LA“到最后一个字段”Env",判断每个字段的值是否在平均值加减3倍标准差范围内,在则保留这个观测值,不在则删除整行,请使用tidyverse完成代码

DeepSeek的思考过程

好的,我现在需要解决这个问题,用户给了一个数据框leafdata_A,里面有22个字段,从第三个字段“LA”到最后一个字段“Env”需要处理。目标是检查每一行中这些字段的值是否都在各自列的均值±3倍标准差范围内,如果是就保留,否则删除整行。用户要求用tidyverse包来完成。

首先,我得回忆一下tidyverse中的函数,尤其是dplyr的处理方式。可能需要用rowwise()操作,或者结合across()来逐行判断。但均值加减3倍标准差的范围是针对每个变量单独计算的,所以需要先算出每个变量的均值和标准差,然后对每个观测值进行检查。

那步骤大概是这样的:

  1. 对每个需要检查的列计算均值和标准差。

  2. 对每个观测行,检查这些列中的每个值是否都在对应的均值±3倍标准差范围内。

  3. 如果所有被检查的列都满足条件,则保留该行;否则删除。

但如何在dplyr中实现呢?可能需要用到across()结合cur_column()来动态获取当前列的名称,然后计算对应的均值和标准差。不过,在mutate中直接引用列的统计量可能会有问题,因为需要先计算这些统计量。

可能的思路是先创建一个包含各列均值和标准差的汇总数据框,然后在原始数据框中进行行过滤,对于每一行,检查所有相关列是否满足条件。例如,可以用pmap或者rowwise()配合c_across()来实现。

或者,可以分两步:

  • 第一步,为每个需要检查的列生成逻辑向量,判断该行的该列是否在范围内。

  • 第二步,对所有生成的逻辑向量进行行方向的判断,即所有列都满足条件时才保留。

但如何动态地处理每个列的均值和标准差呢?比如,对每个列,计算mean和sd,然后生成对应的条件。

比如,用across()来选择从LA到Env的所有列,然后对每个列应用一个函数,该函数返回该值是否在均值的±3倍标准差范围内。但问题在于,在mutate中使用across时,如何同时引用该列的均值和标准差?

可能需要先计算各列的均值和标准差,存储为一个列表或数据框,然后在过滤的时候引用这些值。

例如,先计算各列的均值和标准差:

summary_stats %

summarise(across(LA:Env, list(mean = mean, sd = sd), .names = "{.col}_{.fn}"))

这样,summary_stats是一个单行的数据框,列名如LA_mean, LA_sd,Env_mean, Env_sd等。

然后,在过滤的时候,需要针对每一行的每个列,检查是否在该列的mean±3*sd范围内。这时候,可能需要用到rowwise()和c_across(),或者在mutate中逐列处理。

或者,可以用purrr的map函数来逐列处理,生成一个逻辑矩阵,然后应用rowSums或rowMeans来判断是否所有列都满足条件。

或者,另一种思路是,在过滤条件中使用across(),并且对于每个列x,x应满足x > mean(x) - 3 sd(x) 并且 x < mean(x) + 3 sd(x)。但问题是在dplyr的filter函数中,这样直接引用mean(x)可能会有问题,因为当在across里面使用mean(x)时,这里的x是整个列的数据,而此时的上下文可能不是正确的。例如,当用across处理每一列的时候,mean(x)会被计算为当前列的全局均值吗?

比如,假设在filter中,这样写:

leafdata_A %>%

filter(across(LA:Env, ~ between(.x, mean(.x) - 3 * sd(.x), mean(.x) + 3 * sd(.x))))

但是,这里的问题是,当使用across的时候,每个列的mean和sd是基于整个列计算的,这在filter函数中是可行的,因为此时整个数据框的所有行都被考虑在内。而filter函数会逐行处理,判断每个元素是否满足条件。但这样的话,across中的每个条件会被独立计算,即对于每一行中的某个列的值,是否在该列的均值的±3sd范围内。然后,filter要求所有列的条件都为TRUE才会保留该行。

这样是否可行?需要测试一下。例如,假设某列LA的均值是m,sd是s,那么对于每一行的LA值,是否在m-3s到m+3s之间。如果所有被检查的列都满足这个条件,则保留该行。

但这样写的话,across中的每个条件会被计算正确吗?比如,mean(.x)在这里会被计算为整个列的均值,还是当前行的某个值?显然,在R中,.x代表的是整个列的数据,所以mean(.x)会得到整个列的均值,而sd(.x)同理。所以,这样的表达式应该能够正确计算每个列的均值和标准差,然后对每个元素进行检查。

不过,这样的话,在filter中使用across(LA:Env, ~ ...) 会为每个列生成一个逻辑向量,然后filter函数会将这些条件用&连接起来,即要求所有列的条件都为TRUE。这正是我们需要的。

那这样的话,整个代码可以写成:

library(tidyverse)

leafdata_A_clean %

filter(across(LA:Env, ~ between(.x,

mean(.x, na.rm = TRUE) - 3 * sd(.x, na.rm = TRUE),

mean(.x, na.rm = TRUE) + 3 * sd(.x, na.rm = TRUE))))

不过,这里需要注意,如果某些列有缺失值,可能需要设置na.rm = TRUE。用户的数据是否有NA?题目中没有说明,但为了稳健性,可能需要处理。用户的数据是否有NA可能会影响结果,所以最好在计算mean和sd时处理NA,假设用户的数据中没有NA,或者用户希望排除NA的情况。

另外,between函数是包括端点的,即闭区间。这样是否符合用户的要求?题目中说是“在平均值加减3倍标准差范围内”,可能包括等于的情况,所以正确。

所以,这样的代码应该能够满足用户的需求:对于每一行,检查每个从LA到Env的列的值是否在对应列的均值±3倍标准差范围内,如果所有列都满足,则保留该行,否则删除。

那这样是否可行?应该可以。但需要测试一下。

比如,假设有一个数据框:

df

Location = c("A", "B", "C"),

TreeID = 1:3,

LA = c(1, 2, 100),

LBA = c(2, 3, 4)

)

这里,假设第三列LA的均值是 (1+2+100)/3 = 34.333,标准差是 sqrt( ( (1-34.333)^2 + (2-34.333)^2 + (100-34.333)^2 ) /2 ) ≈ 计算的话,可能标准差很大。比如,第三个值100可能超出了均值±3sd的范围。

运行上面的代码:

df %>%

filter(across(LA:LBA, ~ between(.x, mean(.x) - 3 * sd(.x), mean(.x) + 3 * sd(.x))))

结果应该只保留前两行,因为第三行的LA值可能超出了3个标准差的范围。

所以这样的方法是可行的。

但需要注意,当在across中使用mean和sd时,这些统计量是基于整个列的,包括在过滤之前的所有数据。也就是说,这里是否存在循环依赖?例如,如果先过滤掉某些行,那么计算出的均值和标准差会变化。但在这个方法中,均值和标准差是基于原始数据框的所有行计算的,而不是在过滤后的子集。所以是否正确?

用户的要求是判断每个字段的值是否在原始数据中的平均值加减3倍标准差范围内,所以应该使用原始数据计算的均值和标准差,而不是在删除某些行后重新计算。所以,这样的处理是正确的。

所以,最终的代码应该是这样:

library(tidyverse)

leafdata_A_clean %

filter(across(LA:Env, ~ between(.x,

mean(.x, na.rm = TRUE) - 3 * sd(.x, na.rm = TRUE),

mean(.x, na.rm = TRUE) + 3 * sd(.x, na.rm = TRUE))))

不过,这里可能要注意的是,如何指定列的范围。用户的数据中,第三个字段是LA,最后一个字段是Env,所以LA:Env应该能正确选择这些列。但需要确保列的顺序正确,或者使用更可靠的方法,比如通过位置来选择,例如3:22,或者使用everything()结合relational selection。比如,如果列的顺序是Location, TreeID, LA, ..., Env,则从第三个到最后一个可以用-(1:2)来选择,或者用LA:Env如果列名是连续的。

假设用户的数据框中,列的顺序确实是Location, TreeID, LA,..., Env,所以LA到Env是正确的列范围。

总结一下,正确的代码应该是:

使用dplyr的filter函数和across,对每个选中的列应用一个函数,检查该值是否在均值的±3倍标准差范围内。注意处理可能的NA值,使用na.rm=TRUE,但若数据中没有NA则可以忽略。

最后,生成的新数据框leafdata_A_clean即为满足条件的行。

DeepSeek的回答

以下是使用tidyverse包实现该需求的代码:

library(tidyverse)

leafdata_A_clean %
  filter(across(LA:Env, ~ between(.x, 
                                  mean(.x, na.rm = TRUE) - 3 * sd(.x, na.rm = TRUE),
                                  mean(.x, na.rm = TRUE) + 3 * sd(.x, na.rm = TRUE)))

代码解释:

  1. across(LA:Env) :选取从LA到Env的所有数值型列
  2. ~ between(.x, ...) :对每个选中的列进行范围判断
  3. mean(.x, na.rm = TRUE) ± 3 * sd(.x, na.rm = TRUE) :计算每列的均值±3倍标准差范围
  4. filter() :保留所有列都在合理范围内的行

注意事项:

  1. na.rm = TRUE 会忽略NA值计算均值和标准差,如果数据中存在NA需要特殊处理
  2. between() 是闭区间函数,包含端点值
  3. 最终结果是所有被检查列都满足条件的观测值才会被保留
  4. 原数据框的前两列(Location和TreeID)会被自动保留,不需要特殊处理

如果需要更严格的开放区间判断(不包含端点),可以用 > < 代替 between()

leafdata_A_clean %
  filter(across(LA:Env, ~ (.x > (mean(.x, na.rm = TRUE) - 3 * sd(.x, na.rm = TRUE)) &
                    (.x TRUE) + 3 * sd(.x, na.rm = TRUE)))))

本地DeepSeek-R1-Qwen对代码的解释

好的,我需要解释如何在R语言中使用`tidyverse`库中的函数来实现基于3σ法则的数据清洗。首先,我会回顾一下之前的对话历史和用户的要求。

用户的初始请求是希望从数据框 leafdata_A 中筛选出满足每个字段的值都在平均值加减3倍标准差范围内的行,并且要求使用 tidyverse 库完成这个任务。在之前的交流中,我提供了一个逐步的解决方案,详细解释了每一步的操作和背后的逻辑。

然而,用户随后指出了一段更为简洁的代码,并认为这是正确的答案。这段代码使用了 filter across 函数来同时处理多个字段,并应用3σ法则进行过滤。这表明用户对 tidyverse 中更高级的函数有一定的了解,可能已经接触过类似的数据清洗操作。

现在,用户的最新请求是希望结合上面的代码,解释3σ法则(即三西格马定律)以及相关的数据清洗精度概念。这意味着用户不仅需要知道如何执行这些操作,还希望理解这些方法背后的统计学原理和应用场景。







请到「今天看啥」查看全文