如何有效地将位于一个 sheet 中的多个 excel 表导入到 R 列表中?
How to efficiently import multiple excel tables located in one sheet into an R list?
任务
我正在尝试将位于单个 excel sheet 中的 table 尽可能高效地导入到 R
对象中(list
将很好,因为我可以从那里进行其余的计算)。
细微差别
table 实际上是 excel ranges
而不是 excel tables
,但它们的结构看起来像 table:这里是 [=50= 的示例] 范围应该在 R:
中作为 table 导入
范围(在 table 形式中)长度不同,可以位于相同 sheet.
中的任何位置
可重现的例子
Here 你可以找到一个玩具示例(.xlsx 文件)来玩:
我试过的
这是我编写的用于将 excel tables 导入 R
的代码。这是一种低效的方法,因为它需要在 运行 此代码之前将所有 excel 范围转换为 tables 以将它们导入 R:
中的列表
library(purrr)
library(XLConnect)
wb <- loadWorkbook("example.xlsx")
tables <- map(1:100,function(x) tryCatch(readTable(wb,
sheet = "Sheet1",
table = paste0("Table",x)),
error = function(e) NA)
)
问题
是否有更好(更有效)的方式将一个 excel sheet 中的范围导入到 R
结构中,方法是将 excel
文件作为给定的并且 运行 R
中的所有 computations/transformations。欢迎任何包裹!
非常感谢您。
我不确定我是否使用了最好的方法,但是为了解决我的一个项目中的类似问题。我写了一些实用函数来处理它。You can see those functions here
拆分背后的逻辑是,只要有仅包含 NA 的行或列,就会在该行或列上创建拆分。而且这个过程会进行一定的次数。
无论如何,如果你加载我写的所有功能,你可以使用下面的代码:
读取数据
library(tidyverse)
table_raw<- readxl::read_excel("example.xlsx",col_names = FALSE,col_types = "text")
显示数据形状
# This is a custom function I wrote
display_table_shape(table_raw)
将数据拆分为单独的数据帧。
split_table <- table_raw %>%
split_df(complexity = 2) # another custom function I wrote
原始数据框拆分后,可以使用for循环或map函数做更多处理。
数据清理
map(split_table, function(df){
df <- df[-1,]
set_1row_colname(df) %>% # another function I wrote
mutate_all(as.numeric)
})
结果
[[1]]
# A tibble: 8 x 4
aa bb cc dd
<dbl> <dbl> <dbl> <dbl>
1 0.197 0.321 0.265 0.0748
2 0.239 0.891 0.0308 0.453
3 0.300 0.779 0.780 0.213
4 0.132 0.138 0.612 0.0362
5 0.834 0.697 0.879 0.571
6 0.956 0.807 0.741 0.936
7 0.359 0.536 0.0902 0.764
8 0.403 0.315 0.593 0.840
[[2]]
# A tibble: 4 x 4
aa bb cc dd
<dbl> <dbl> <dbl> <dbl>
1 0.136 0.347 0.603 0.542
2 0.790 0.672 0.0808 0.795
3 0.589 0.338 0.837 0.00968
4 0.513 0.766 0.553 0.189
[[3]]
# A tibble: 8 x 4
aa bb cc dd
<dbl> <dbl> <dbl> <dbl>
1 0.995 0.105 0.106 0.530
2 0.372 0.306 0.190 0.609
3 0.508 0.987 0.585 0.233
4 0.0800 0.851 0.215 0.761
5 0.471 0.603 0.740 0.106
6 0.395 0.0808 0.571 0.266
7 0.908 0.739 0.245 0.141
8 0.534 0.313 0.663 0.824
[[4]]
# A tibble: 14 x 4
aa bb cc dd
<dbl> <dbl> <dbl> <dbl>
1 0.225 0.993 0.0382 0.412
2 0.280 0.202 0.823 0.664
3 0.423 0.616 0.377 0.857
4 0.289 0.298 0.0418 0.410
5 0.919 0.932 0.882 0.668
6 0.568 0.561 0.600 0.832
7 0.341 0.210 0.351 0.0863
8 0.757 0.962 0.484 0.677
9 0.275 0.0845 0.824 0.571
10 0.187 0.512 0.884 0.612
11 0.706 0.311 0.00610 0.463
12 0.906 0.411 0.215 0.377
13 0.629 0.317 0.0975 0.312
14 0.144 0.644 0.906 0.353
您需要加载的功能
# utility function to get rle as a named vector
vec_rle <- function(v){
temp <- rle(v)
out <- temp$values
names(out) <- temp$lengths
return(out)
}
# utility function to map table with their columns/rows in a bigger table
make_df_index <- function(v){
table_rle <- vec_rle(v)
divide_points <- c(0,cumsum(names(table_rle)))
table_index <- map2((divide_points + 1)[1:length(divide_points)-1],
divide_points[2:length(divide_points)],
~.x:.y)
return(table_index[table_rle])
}
# split a large table in one direction if there are blank columns or rows
split_direction <- function(df,direction = "col"){
if(direction == "col"){
col_has_data <- unname(map_lgl(df,~!all(is.na(.x))))
df_mapping <- make_df_index(col_has_data)
out <- map(df_mapping,~df[,.x])
} else if(direction == "row"){
row_has_data <- df %>%
mutate_all(~!is.na(.x)) %>%
as.matrix() %>%
apply(1,any)
df_mapping <- make_df_index(row_has_data)
out <- map(df_mapping,~df[.x,])
}
return(out)
}
# split a large table into smaller tables if there are blank columns or rows
# if you still see entire rows or columns missing. Please increase complexity
split_df <- function(df,showWarnig = TRUE,complexity = 1){
if(showWarnig){
warning("Please don't use first row as column names.")
}
out <- split_direction(df,"col")
for(i in 1 :complexity){
out <- out %>%
map(~split_direction(.x,"row")) %>%
flatten() %>%
map(~split_direction(.x,"col")) %>%
flatten()
}
return(out)
}
#display the rough shape of table in a sheet with multiple tables
display_table_shape <- function(df){
colnames(df) <- 1:ncol(df)
out <- df %>%
map_df(~as.numeric(!is.na(.x))) %>%
gather(key = "x",value = "value") %>%
mutate(x = as.numeric(x)) %>%
group_by(x) %>%
mutate(y = -row_number()) %>%
ungroup() %>%
filter(value == 1) %>%
ggplot(aes(x = x, y = y,fill = value)) +
geom_tile(fill = "skyblue3") +
scale_x_continuous(position = "top") +
theme_void() +
theme(legend.position="none",
panel.border = element_rect(colour = "black", fill=NA, size=2))
return(out)
}
# set first row as column names for a data frame and remove the original first row
set_1row_colname <- function(df){
colnames(df) <- as.character(df[1,])
out <- df[-1,]
return(out)
}
我遇到了类似的问题,我就是这样解决的。请注意,它失去了 yusuzech 答案的一些好处,因为它确实需要您指定感兴趣的范围。另一方面,它可能会更高效地编码并更适应不同的情况。
# specify the ranges you want to import from the excel sheet
v_ranges <- c("A3:F54", "H3:M54", "O3:T54", "V3:AA54", "AC3:AH54")
# specify the names of the dataframes
v_names <- c("21Q3", "21Q2", "21Q1", "20Q4", "20Q3")
# specify sheet and path
v_path_file <- "my_path/my_excel_file.xlsx"
v_sheet <- "my_sheet_name"
# define the import function, with v_ranges as your ranges of interest, v_path_file as the excel workbook you want to import from, and v_sheet the sheet name of the file
f_import_excel_by_range <- function(.x) {
janitor::clean_names(
readxl::read_excel(v_path_file,
sheet = v_sheet,
range = .x,
col_names = TRUE, na = c(" ", "NA"), trim_ws = TRUE, skip = 1)
)
}
my_file_name <-
purrr::map(v_ranges, f_import_excel_by_range) %>%
purrr::set_names(paste0("my_file_name_",v_names))
# extract databases to the environment
base::invisible(base::list2env(my_file_name, .GlobalEnv))
我相信可以通过在函数中包含路径和文件以及 sheet 名称来改进此函数。如果我侦查出来,我会编辑。欢迎反馈。
任务
我正在尝试将位于单个 excel sheet 中的 table 尽可能高效地导入到 R
对象中(list
将很好,因为我可以从那里进行其余的计算)。
细微差别
table 实际上是 excel ranges
而不是 excel tables
,但它们的结构看起来像 table:这里是 [=50= 的示例] 范围应该在 R:
范围(在 table 形式中)长度不同,可以位于相同 sheet.
中的任何位置可重现的例子
Here 你可以找到一个玩具示例(.xlsx 文件)来玩:
我试过的
这是我编写的用于将 excel tables 导入 R
的代码。这是一种低效的方法,因为它需要在 运行 此代码之前将所有 excel 范围转换为 tables 以将它们导入 R:
library(purrr)
library(XLConnect)
wb <- loadWorkbook("example.xlsx")
tables <- map(1:100,function(x) tryCatch(readTable(wb,
sheet = "Sheet1",
table = paste0("Table",x)),
error = function(e) NA)
)
问题
是否有更好(更有效)的方式将一个 excel sheet 中的范围导入到 R
结构中,方法是将 excel
文件作为给定的并且 运行 R
中的所有 computations/transformations。欢迎任何包裹!
非常感谢您。
我不确定我是否使用了最好的方法,但是为了解决我的一个项目中的类似问题。我写了一些实用函数来处理它。You can see those functions here
拆分背后的逻辑是,只要有仅包含 NA 的行或列,就会在该行或列上创建拆分。而且这个过程会进行一定的次数。
无论如何,如果你加载我写的所有功能,你可以使用下面的代码:
读取数据
library(tidyverse)
table_raw<- readxl::read_excel("example.xlsx",col_names = FALSE,col_types = "text")
显示数据形状
# This is a custom function I wrote
display_table_shape(table_raw)
将数据拆分为单独的数据帧。
split_table <- table_raw %>%
split_df(complexity = 2) # another custom function I wrote
原始数据框拆分后,可以使用for循环或map函数做更多处理。
数据清理
map(split_table, function(df){
df <- df[-1,]
set_1row_colname(df) %>% # another function I wrote
mutate_all(as.numeric)
})
结果
[[1]]
# A tibble: 8 x 4
aa bb cc dd
<dbl> <dbl> <dbl> <dbl>
1 0.197 0.321 0.265 0.0748
2 0.239 0.891 0.0308 0.453
3 0.300 0.779 0.780 0.213
4 0.132 0.138 0.612 0.0362
5 0.834 0.697 0.879 0.571
6 0.956 0.807 0.741 0.936
7 0.359 0.536 0.0902 0.764
8 0.403 0.315 0.593 0.840
[[2]]
# A tibble: 4 x 4
aa bb cc dd
<dbl> <dbl> <dbl> <dbl>
1 0.136 0.347 0.603 0.542
2 0.790 0.672 0.0808 0.795
3 0.589 0.338 0.837 0.00968
4 0.513 0.766 0.553 0.189
[[3]]
# A tibble: 8 x 4
aa bb cc dd
<dbl> <dbl> <dbl> <dbl>
1 0.995 0.105 0.106 0.530
2 0.372 0.306 0.190 0.609
3 0.508 0.987 0.585 0.233
4 0.0800 0.851 0.215 0.761
5 0.471 0.603 0.740 0.106
6 0.395 0.0808 0.571 0.266
7 0.908 0.739 0.245 0.141
8 0.534 0.313 0.663 0.824
[[4]]
# A tibble: 14 x 4
aa bb cc dd
<dbl> <dbl> <dbl> <dbl>
1 0.225 0.993 0.0382 0.412
2 0.280 0.202 0.823 0.664
3 0.423 0.616 0.377 0.857
4 0.289 0.298 0.0418 0.410
5 0.919 0.932 0.882 0.668
6 0.568 0.561 0.600 0.832
7 0.341 0.210 0.351 0.0863
8 0.757 0.962 0.484 0.677
9 0.275 0.0845 0.824 0.571
10 0.187 0.512 0.884 0.612
11 0.706 0.311 0.00610 0.463
12 0.906 0.411 0.215 0.377
13 0.629 0.317 0.0975 0.312
14 0.144 0.644 0.906 0.353
您需要加载的功能
# utility function to get rle as a named vector
vec_rle <- function(v){
temp <- rle(v)
out <- temp$values
names(out) <- temp$lengths
return(out)
}
# utility function to map table with their columns/rows in a bigger table
make_df_index <- function(v){
table_rle <- vec_rle(v)
divide_points <- c(0,cumsum(names(table_rle)))
table_index <- map2((divide_points + 1)[1:length(divide_points)-1],
divide_points[2:length(divide_points)],
~.x:.y)
return(table_index[table_rle])
}
# split a large table in one direction if there are blank columns or rows
split_direction <- function(df,direction = "col"){
if(direction == "col"){
col_has_data <- unname(map_lgl(df,~!all(is.na(.x))))
df_mapping <- make_df_index(col_has_data)
out <- map(df_mapping,~df[,.x])
} else if(direction == "row"){
row_has_data <- df %>%
mutate_all(~!is.na(.x)) %>%
as.matrix() %>%
apply(1,any)
df_mapping <- make_df_index(row_has_data)
out <- map(df_mapping,~df[.x,])
}
return(out)
}
# split a large table into smaller tables if there are blank columns or rows
# if you still see entire rows or columns missing. Please increase complexity
split_df <- function(df,showWarnig = TRUE,complexity = 1){
if(showWarnig){
warning("Please don't use first row as column names.")
}
out <- split_direction(df,"col")
for(i in 1 :complexity){
out <- out %>%
map(~split_direction(.x,"row")) %>%
flatten() %>%
map(~split_direction(.x,"col")) %>%
flatten()
}
return(out)
}
#display the rough shape of table in a sheet with multiple tables
display_table_shape <- function(df){
colnames(df) <- 1:ncol(df)
out <- df %>%
map_df(~as.numeric(!is.na(.x))) %>%
gather(key = "x",value = "value") %>%
mutate(x = as.numeric(x)) %>%
group_by(x) %>%
mutate(y = -row_number()) %>%
ungroup() %>%
filter(value == 1) %>%
ggplot(aes(x = x, y = y,fill = value)) +
geom_tile(fill = "skyblue3") +
scale_x_continuous(position = "top") +
theme_void() +
theme(legend.position="none",
panel.border = element_rect(colour = "black", fill=NA, size=2))
return(out)
}
# set first row as column names for a data frame and remove the original first row
set_1row_colname <- function(df){
colnames(df) <- as.character(df[1,])
out <- df[-1,]
return(out)
}
我遇到了类似的问题,我就是这样解决的。请注意,它失去了 yusuzech 答案的一些好处,因为它确实需要您指定感兴趣的范围。另一方面,它可能会更高效地编码并更适应不同的情况。
# specify the ranges you want to import from the excel sheet
v_ranges <- c("A3:F54", "H3:M54", "O3:T54", "V3:AA54", "AC3:AH54")
# specify the names of the dataframes
v_names <- c("21Q3", "21Q2", "21Q1", "20Q4", "20Q3")
# specify sheet and path
v_path_file <- "my_path/my_excel_file.xlsx"
v_sheet <- "my_sheet_name"
# define the import function, with v_ranges as your ranges of interest, v_path_file as the excel workbook you want to import from, and v_sheet the sheet name of the file
f_import_excel_by_range <- function(.x) {
janitor::clean_names(
readxl::read_excel(v_path_file,
sheet = v_sheet,
range = .x,
col_names = TRUE, na = c(" ", "NA"), trim_ws = TRUE, skip = 1)
)
}
my_file_name <-
purrr::map(v_ranges, f_import_excel_by_range) %>%
purrr::set_names(paste0("my_file_name_",v_names))
# extract databases to the environment
base::invisible(base::list2env(my_file_name, .GlobalEnv))
我相信可以通过在函数中包含路径和文件以及 sheet 名称来改进此函数。如果我侦查出来,我会编辑。欢迎反馈。