Dividir marco de datos por fila vacía
Estoy tratando de dividir un marco de datos horriblemente formateado en una lista de marcos de datos basada en las filas de NA entre bloques, es decir, Loc_1, Loc_2, Loc_3. Intenté dividir los marcos de datos en R en función de filas vacías y dividir o dividir el marco de datos en varios archivos dfs en función de la fila vacía y el título del encabezado sin suerte. Creo que la diferencia en mi caso es que no tengo una sola columna sin un valor NA, ya que cada nuevo fragmento comienza con NA para dos filas en las dos primeras columnas, y hay muchas NA dispersas por todas partes. ¿Algunas ideas? Esta es mi primera publicación, ¡así que griten si necesito publicar más información!
df <- data.frame(
a = c(NA, NA, "Loc_1", "Loc_1", "Loc_1", NA, NA, NA, "Loc_2", "Loc_2", "Loc_2", NA, NA, NA, "Loc_3", "Loc_3", "Loc_3"),
b = c(NA, NA, "25:11:2020", "26:11:2020", "27:11:2020", NA, NA, NA, "25:11:2020", "26:11:2020", "27:11:2020",NA, NA, NA, "25:11:2020", "26:11:2020", "27:11:2020"),
c = c("Var1", "Unit/1", 1:3, NA, "Var3", "Unit/3", NA, 1, 2, NA,"Var1", "Unit/1", 1:3),
d = c("Var2", "Unit/2", NA, NA, 1, NA, "Var1", "Unit/1", NA, NA, 1, NA, "Var3", "Unit/3", NA, NA, 1)
)
a b c d
1 <NA> <NA> Var1 Var2
2 <NA> <NA> Unit/1 Unit/2
3 Loc_1 25:11:2020 1 <NA>
4 Loc_1 26:11:2020 2 <NA>
5 Loc_1 27:11:2020 3 1
6 <NA> <NA> <NA> <NA>
7 <NA> <NA> Var3 Var1
8 <NA> <NA> Unit/3 Unit/1
9 Loc_2 25:11:2020 <NA> <NA>
10 Loc_2 26:11:2020 1 <NA>
11 Loc_2 27:11:2020 2 1
12 <NA> <NA> <NA> <NA>
13 <NA> <NA> Var1 Var3
14 <NA> <NA> Unit/1 Unit/3
15 Loc_3 25:11:2020 1 <NA>
16 Loc_3 26:11:2020 2 <NA>
17 Loc_3 27:11:2020 3 1
Respuestas
¿Qué tal esta solución Base R:
n <- rowSums(is.na(df)) == ncol(df)
cs <- cumsum(n) + 1
s <- split(df[!n, ], cs[!n])
s
#> $`1` #> a b c d #> 1 <NA> <NA> Var1 Var2 #> 2 <NA> <NA> Unit/1 Unit/2 #> 3 Loc_1 25:11:2020 1 <NA> #> 4 Loc_1 26:11:2020 2 <NA> #> 5 Loc_1 27:11:2020 3 1 #> #> $`2`
#> a b c d
#> 7 <NA> <NA> Var3 Var1
#> 8 <NA> <NA> Unit/3 Unit/1
#> 9 Loc_2 25:11:2020 <NA> <NA>
#> 10 Loc_2 26:11:2020 1 <NA>
#> 11 Loc_2 27:11:2020 2 1
#>
#> $`3`
#> a b c d
#> 13 <NA> <NA> Var1 Var3
#> 14 <NA> <NA> Unit/1 Unit/3
#> 15 Loc_3 25:11:2020 1 <NA>
#> 16 Loc_3 26:11:2020 2 <NA>
#> 17 Loc_3 27:11:2020 3 1
Puede configurar ordenadamente todos sus datos juntos nuevamente en un formato largo de esta manera con unpivotr
:
library(unpivotr)
library(dplyr)
library(purrr)
map_dfr(s,
~ as_cells(.x) %>%
behead("up", "var") %>%
behead("up", "uom") %>%
behead("left", "loc") %>%
behead("left", "date") %>%
# filter(!is.na(chr)) %>% # do you need the NAs?
mutate(value = as.numeric(chr)) %>%
select(var, uom, loc, date, value),
.id = "df")
#> # A tibble: 18 x 6
#> df var uom loc date value
#> <chr> <chr> <chr> <chr> <chr> <dbl>
#> 1 1 Var1 Unit/1 Loc_1 25:11:2020 1
#> 2 1 Var1 Unit/1 Loc_1 26:11:2020 2
#> 3 1 Var1 Unit/1 Loc_1 27:11:2020 3
#> 4 1 Var2 Unit/2 Loc_1 25:11:2020 NA
#> 5 1 Var2 Unit/2 Loc_1 26:11:2020 NA
#> 6 1 Var2 Unit/2 Loc_1 27:11:2020 1
#> 7 2 Var3 Unit/3 Loc_2 25:11:2020 NA
#> 8 2 Var3 Unit/3 Loc_2 26:11:2020 1
#> 9 2 Var3 Unit/3 Loc_2 27:11:2020 2
#> 10 2 Var1 Unit/1 Loc_2 25:11:2020 NA
#> 11 2 Var1 Unit/1 Loc_2 26:11:2020 NA
#> 12 2 Var1 Unit/1 Loc_2 27:11:2020 1
#> 13 3 Var1 Unit/1 Loc_3 25:11:2020 1
#> 14 3 Var1 Unit/1 Loc_3 26:11:2020 2
#> 15 3 Var1 Unit/1 Loc_3 27:11:2020 3
#> 16 3 Var3 Unit/3 Loc_3 25:11:2020 NA
#> 17 3 Var3 Unit/3 Loc_3 26:11:2020 NA
#> 18 3 Var3 Unit/3 Loc_3 27:11:2020 1
Si no desea un marco de datos único al final, use en map
lugar de map_dfr
y elimine, .id = "df"
Tu podrías intentar:
library(dplyr)
library(purrr)
df %>%
group_split(grp = cumsum(rowSums(is.na(.)) == ncol(.)), .keep = FALSE) %>%
map_at(.at = -1, tail, -1)
[[1]]
# A tibble: 5 x 4
a b c d
<chr> <chr> <chr> <chr>
1 NA NA Var1 Var2
2 NA NA Unit/1 Unit/2
3 Loc_1 25:11:2020 1 NA
4 Loc_1 26:11:2020 2 NA
5 Loc_1 27:11:2020 3 1
[[2]]
# A tibble: 5 x 4
a b c d
<chr> <chr> <chr> <chr>
1 NA NA Var3 Var1
2 NA NA Unit/3 Unit/1
3 Loc_2 25:11:2020 NA NA
4 Loc_2 26:11:2020 1 NA
5 Loc_2 27:11:2020 2 1
[[3]]
# A tibble: 5 x 4
a b c d
<chr> <chr> <chr> <chr>
1 NA NA Var1 Var3
2 NA NA Unit/1 Unit/3
3 Loc_3 25:11:2020 1 NA
4 Loc_3 26:11:2020 2 NA
5 Loc_3 27:11:2020 3 1
No estoy seguro de la salida deseada. Esta es mi mejor suposición. Agregué más código tratando de eliminar las dos primeras filas para cada una, loc
ya que son solo nombres de columna, y luego asigné los nuevos nombres de columna según la primera fila original. Este paso adicional le permite convertir las Var
columnas a numéricas.
library(tidyverse)
# A helper function to filter rows with any non-NA values
rowAny <- function(x) rowSums(x) > 0
df_list <- df %>%
# Remove rows with all NA
filter(rowAny(across(everything(), .fns = function(x) !is.na(x)))) %>%
# Fill the Loc information
fill(a, .direction = "up") %>%
# Split the data frame by a
split(.$a) %>% # Remove the first two rows and change the column names to the first row (Var1, Var2, Var3, ...) map(function(x){ # Prepare new column names x2 <- x %>% slice(1) %>% t() %>% as.vector() x_names <- c(names(x)[1:2], x2[3:length(x2)]) # Remove the first two rows and assign new column names x3 <- x %>% slice(-1:-2) %>% set_names(x_names) %>% # Change the columns to numeric mutate(across(x2[3:length(x2)], .fns = as.numeric)) return(x3) }) df_list # $Loc_1
# a b Var1 Var2
# 1 Loc_1 25:11:2020 1 NA
# 2 Loc_1 26:11:2020 2 NA
# 3 Loc_1 27:11:2020 3 1
#
# $Loc_2 # a b Var3 Var1 # 1 Loc_2 25:11:2020 NA NA # 2 Loc_2 26:11:2020 1 NA # 3 Loc_2 27:11:2020 2 1 # # $Loc_3
# a b Var1 Var3
# 1 Loc_3 25:11:2020 1 NA
# 2 Loc_3 26:11:2020 2 NA
# 3 Loc_3 27:11:2020 3 1