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

R语言:数据框列表提取对角线遇NA问题及简化实现

问题描述

拥有8个调查站点A至H、9个年份(1999、2000、2001、2006、2008、2011、2013、2016、2019)的数据,先通过以下代码生成模拟数据:

library (dplyr)
# create column names for years
years <- c(1999, 2000, 2001, 2006, 2008, 2011, 2013, 2016, 2019)

# create an empty data frame with sites A:H in the first column
df <- data.frame(Site = c("A", "B", "C", "D", "E", "F", "G", "H"),
                 matrix(NA, nrow = 8, ncol = length(years), 
                        dimnames = list(NULL, years)))

# remove the X before the year in the column names
colnames(df)[-1] <- years

# generate random numbers for each site and year
set.seed(123)  # for reproducibility
for (i in 1:nrow(df)) {
  df[i, 2:ncol(df)] <- runif(length(years))
}

随后计算各站点和年份间的欧氏距离,将距离矩阵转为数据框并按Site拆分为数据框列表:

# gather to long form to calculate dist.
df = df %>% gather (key = "Year", value = "Value", c(2:last_col()))

# calculate dist and set as a df
dist.df <- df %>% 
  mutate(YrSi = paste(substr(Year, 3, 4), Site)) %>%
  select(-Year, -Site) %>%
  column_to_rownames(var = "YrSi") %>%
  dist() %>%
  as.matrix() %>%
  as.data.frame()

#split dist.df to a list of dfs per site

dist.df.list = dist.df %>% 
  rownames_to_column("YrSi") %>% 
  separate(YrSi, c("Year", "Site"), sep = " ") %>% 
  mutate (Year =  as.numeric (ifelse (Year == "99", sprintf("19%s", Year),sprintf("20%s", Year)))) %>% # Change to yyyy
  gather(key = "YrSi", value = "Dist", c(3:last_col())) %>% 
  separate(YrSi, c("Year2", "Site2"), sep = " ") %>% 
  mutate (Year2 =  as.numeric (ifelse (Year2 == "99", sprintf("19%s", Year2),sprintf("20%s", Year2)))) %>% # Change to yyyy
  arrange(Site, Year, Site2, Year2) %>%
  spread (key = "Year2", value = "Dist") %>% 
  group_by(Site, Site2) %>% 
  subset (Site == Site2) %>%
  relocate (Year, .after = Site2) %>% 
  group_split() 

想要创建新数据框result.df,第一列为Site,后续列为2000年及以后的调查年份,每列对应站点该年份与前一调查年份的欧氏距离,即提取数据框中的对角线数据。为便于处理,将数据转为长格式:

dist.df.list = dist.df %>% 
  rownames_to_column("YrSi") %>% 
  separate(YrSi, c("Year", "Site"), sep = " ") %>% 
  mutate (Year =  as.numeric (ifelse (Year == "99", sprintf("19%s", Year),sprintf("20%s", Year)))) %>% # Change to yyyy
  gather(key = "YrSi", value = "Dist", c(3:last_col())) %>% 
  separate(YrSi, c("Year2", "Site2"), sep = " ") %>% 
  mutate (Year2 =  as.numeric (ifelse (Year2 == "99", sprintf("19%s", Year2),sprintf("20%s", Year2)))) %>% # Change to yyyy
  arrange(Site, Year, Site2, Year2) %>%
  spread (key = "Year2", value = "Dist") %>% 
  group_by(Site, Site2) %>% 
  subset (Site == Site2) %>%
  relocate (Year, .after = Site2) %>% 
  gather (key="Year2", value = "dist", c(4:last_col())) %>%
  group_split() 

接着通过for循环生成result.df:

# Initialize an empty data frame to store the results
result.df <- data.frame(Site = character(), stringsAsFactors = FALSE)

# Loop through each data frame in the list
for(i in 1:length(dist.df.list)) {
  # Extract the site name
  site <- dist.df.list[[i]]$Site[1]
  
  # Initialize a new row for the site in the result data frame
  new.row <- data.frame(Site = site, stringsAsFactors = FALSE)
  
  # Loop through each survey year and extract the distance between consecutive years
  for(j in c(2000, 2001, 2006,2008, 2011, 2013, 2016, 2019)) {
    col.name <- as.character(j)
    if(col.name %in% colnames(dist.df.list[[i]])) {
      # Extract the distance value from the test data frame
      dist <- dist.df.list[[i]] %>% 
        filter(Year2 == j) %>% 
        select(dist) %>% 
        pull()
      
      # If the distance value is missing, set it to NA
      if(is.na(dist)) {
        new.row[[col.name]] <- NA
      } else {
        # Otherwise, add the distance value to the new row
        new.row[[col.name]] <- dist
      }
    } else {
      # If the distance column doesn't exist, set the value to NA
      new.row[[col.name]] <- NA
    }
  }
  
  # Add the new row to the result data frame
  result.df <- rbind(result.df, new.row)
}

但得到的result.df全为NA:

result.df

  Site 2000 2001 2006 2008 2011 2013 2016 2019
1    A   NA   NA   NA   NA   NA   NA   NA   NA
2    B   NA   NA   NA   NA   NA   NA   NA   NA
3    C   NA   NA   NA   NA   NA   NA   NA   NA
4    D   NA   NA   NA   NA   NA   NA   NA   NA
5    E   NA   NA   NA   NA   NA   NA   NA   NA
6    F   NA   NA   NA   NA   NA   NA   NA   NA
7    G   NA   NA   NA   NA   NA   NA   NA   NA
8    H   NA   NA   NA   NA   NA   NA   NA   NA
问题原因分析

全NA的核心原因是类型不匹配导致筛选失败:

  • 在最后一次生成dist.df.list的代码中,gather(key="Year2", value = "dist", c(4:last_col()))将列名(字符型年份,如"2000")转换为Year2列的字符值,但后续循环中用filter(Year2 == j)时,j是数值型(如2000),字符与数值无法匹配,导致筛选结果为空,pull()返回NA。
  • 此外,你的处理逻辑绕了不必要的弯路:计算全局距离矩阵后再拆分筛选,不仅效率低,还容易引入格式、类型错误。
简化实现方法

因为你要的是同一站点自身相邻调查年份的欧氏距离,而单变量的欧氏距离就是两个数值差的绝对值,完全不需要计算全局距离矩阵。直接基于原始数据处理即可,代码简洁且不易出错:

library(dplyr)
library(tidyr)

# 原始模拟数据生成(保留你的代码)
years <- c(1999, 2000, 2001, 2006, 2008, 2011, 2013, 2016, 2019)
df <- data.frame(Site = c("A", "B", "C", "D", "E", "F", "G", "H"),
                 matrix(NA, nrow = 8, ncol = length(years), 
                        dimnames = list(NULL, years)))
colnames(df)[-1] <- years
set.seed(123)
for (i in 1:nrow(df)) {
  df[i, 2:ncol(df)] <- runif(length(years))
}

# 核心处理步骤
result.df <- df %>%
  # 转为长格式
  pivot_longer(cols = -Site, names_to = "Year", values_to = "Value") %>%
  # 年份转数值型
  mutate(Year = as.numeric(Year)) %>%
  # 按站点分组,计算当前年份与前一年份的欧氏距离(单变量即绝对值差)
  group_by(Site) %>%
  arrange(Year, .by_group = TRUE) %>%
  mutate(Dist = abs(Value - lag(Value))) %>%
  # 只保留2000年及以后的距离数据
  filter(Year %in% c(2000, 2001, 2006, 2008, 2011, 2013, 2016, 2019)) %>%
  # 转回宽格式,得到目标结构
  select(Site, Year, Dist) %>%
  pivot_wider(names_from = Year, values_from = Dist) %>%
  ungroup()

# 查看结果
print(result.df)

运行后就能得到正确的非NA结果,逻辑清晰且效率更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 08:27:10