Dividi dataframe per riga vuota
Sto cercando di dividere un dataframe orrendamente formattato in un elenco di dataframe basato sulle righe di NA tra i blocchi, ad esempio Loc_1, Loc_2, Loc_3. Ho provato a dividere i dataframe in R in base a righe vuote e dividere o dividere dataframe in più dfs in base alla riga vuota e al titolo dell'intestazione senza fortuna. Penso che la differenza nel mio caso sia che non ho una singola colonna senza un valore NA, poiché ogni nuovo blocco inizia con NA per due righe nelle prime due colonne e ci sono un sacco di NA sparsi ovunque. Qualche idea? Questo è il mio primo post, quindi per favore urla se ho bisogno di inserire maggiori informazioni!
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
Risposte
Che ne dici di questa soluzione 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
Puoi impostare nuovamente tutti i tuoi dati insieme in un formato lungo in questo modo 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
Se non vuoi un dataframe univoco alla fine, usa mapinvece di map_dfre rimuovi, .id = "df"
Potresti provare:
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
Non sono sicuro dell'output desiderato. Ecco la mia ipotesi migliore. Ho aggiunto più codice cercando di rimuovere le prime due righe per ciascuna in locquanto sono solo nomi di colonna, quindi assegnare i nuovi nomi di colonna in base alla prima riga originale. Questo passaggio aggiuntivo consente di convertire le Varcolonne in numeriche.
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