Transforme a column into a list inside a dataframe - r

I have a dataframe such as
df
COL1 COL2
A "[Lasius_niger]"
B "[Canis_lupus,Feis_cattus]"
C "[Cattus_stigmatizans,Cattus_cattus"]
D "[Apis_mellifera]"
and in my code I iterate each row of df$COL2 into a commande where I need that the cotent is a list.
So I need to transforme the df$COL2 into a list inside the dataframe
So I should get something like that I guess:
COL1 COL2
A "Lasius_niger"
B "Canis_lupus","Feis_cattus"
C "Cattus_stigmatizans","Cattus_cattus"
D "Apis_mellifera"
does someone have an idea ?

Remove opening and closing square brackets using gsub and split string on comma.
df$COL2 <- strsplit(gsub('\\[|\\]', '', df$COL2), ',')
str(df)
#'data.frame': 4 obs. of 2 variables:
# $ COL1: chr "A" "B" "C" "D"
# $ COL2:List of 4
# ..$ : chr "Lasius_niger"
# ..$ : chr "Canis_lupus" "Feis_cattus"
# ..$ : chr "Cattus_stigmatizans" "Cattus_cattus"
# ..$ : chr "Apis_mellifera"
data
df <- structure(list(COL1 = c("A", "B", "C", "D"), COL2 = c("[Lasius_niger]",
"[Canis_lupus,Feis_cattus]", "[Cattus_stigmatizans,Cattus_cattus]",
"[Apis_mellifera]")), class = "data.frame", row.names = c(NA, -4L))

You can also use the function stri_extract_all_words in the stringi package as follows
df$COL2 <- stringi::stri_extract_all_words(df$COL2)
str(df)
#'data.frame': 4 obs. of 2 variables:
# $ COL1: chr "A" "B" "C" "D"
# $ COL2:List of 4
# ..$ : chr "Lasius_niger"
# ..$ : chr "Canis_lupus" "Feis_cattus"
# ..$ : chr "Cattus_stigmatizans" "Cattus_cattus"
# ..$ : chr "Apis_mellifera"

We can also use str_extract_all
library(stringr)
df$COL2 <- str_extract_all(df$COL2, "\\w+")
Or another option from qdapRegex
library(qdapRegex)
rm_square(df$COL2, extract = TRUE)

Related

bind list of lists based on the lists location

I have some data similar to mainList below.
List of 2
$ :List of 3
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "1" "2" "3"
.. .. ..$ col2: chr [1:3] "a" "b" "c"
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "3" "7" "4"
.. .. ..$ col2: chr [1:3] "e" "d" "g"
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "2" "7" "4"
.. .. ..$ col2: chr [1:3] "l" "o" "i"
$ :List of 3
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "8" "3" "4"
.. .. ..$ col2: chr [1:3] "r" "t" "q"
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "7" "5" "2"
.. .. ..$ col2: chr [1:3] "h" "w" "p"
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "9" "3" "6"
.. .. ..$ col2: chr [1:3] "x" "y" "z"
I want to merge, or bind the lists based on the lists location in the list of lists.
That is, I want to merge splt1 with splt11, and then merge splt2 with splt22 and finally splt3 with splt33.
So it would take the first data frame from the first List of 3 and merge it with the first data frame from the second List of 3.
This does not get what I want
mainList %>%
map(., ~bind_rows(., .id = "split"))
Since all of the splits are merged into a single data frame (I want them kept separate).
Data:
splt1 <- list(
data.frame(
col1 = c("1", "2", "3"),
col2 = c("a", "b", "c")
)
)
splt2 <- list(
data.frame(
col1 = c("3", "7", "4"),
col2 = c("e", "d", "g")
)
)
splt3 <- list(
data.frame(
col1 = c("2", "7", "4"),
col2 = c("l", "o", "i")
)
)
nestList1 <- list(
splt1,
splt2,
splt3
)
splt11 <- list(
data.frame(
col1 = c("8", "3", "4"),
col2 = c("r", "t", "q")
)
)
splt22 <- list(
data.frame(
col1 = c("7", "5", "2"),
col2 = c("h", "w", "p")
)
)
splt33 <- list(
data.frame(
col1 = c("9", "3", "6"),
col2 = c("x", "y", "z")
)
)
nestList2 <- list(
splt11,
splt22,
splt33
)
mainList <- list(
nestList1,
nestList2
)
EDIT:
Screenshot of the lists:
I am trying to bind together all of the split's, i.e.
split1 will contain the results from 08001, 08003, 08005 ... 0801501 for each of the lists in catalunya_madrid.
split2 will contain the same results 08001, 08003, 08005 ... 0801501
and so on.
EDIT2:
# Function to invert the list structure
invertListStructure <- function(ll) {
nms <- unique(unlist(lapply(ll, function(X) names(X))))
ll <- lapply(ll, function(X) setNames(X[nms], nms))
ll <- apply(do.call(rbind, ll), 2, as.list)
lapply(ll, function(X) X[!sapply(X, is.null)])
}
invertedList <- map(analysis, ~invertListStructure(.) %>%
map(., ~bind_rows(.x, .id = "MITMA")))
You can use purrr::transpose() to group list elements with the same location (i.e. the first element in list 1 with the first element in list 2 and list 3 and so on) for any number of lists. In your case, transpose will convert 592 lists of 216 into 216 lists of 592, each properly titled. With transpose, l[[x]][[y]] becomes l[[y]][[x]].
library(tidyverse)
mainList %>% purrr::transpose() %>%
map(function(x) {
flatten(x) %>% bind_rows(.id = 'id')
})
# $splt1
# id col1 col2
# 1 1 1 a
# 2 1 2 b
# 3 1 3 c
# 4 2 8 r
# 5 2 3 t
# 6 2 4 q
#
# $splt2
# id col1 col2
# 1 1 3 e
# 2 1 7 d
# 3 1 4 g
# 4 2 7 h
# 5 2 5 w
# 6 2 2 p
#
# $splt3
# id col1 col2
# 1 1 2 l
# 2 1 7 o
# 3 1 4 i
# 4 2 9 x
# 5 2 3 y
# 6 2 6 z
Note that you only need to flatten if the data.frame is in a list of length 1, by itself. If you have a list of data.frames (as opposed to a list of lists, each of which contains one data.frame, as in your example data), you can ignore the flatten() command and just bind the rows.
Your example dataset doesn't quite match your actual data, but if you make a list of two mainLists, it's closer. These types of operations are heavily dependent on the structure of the data, though, so I can't be sure this is what you need. All you need to do here is add a subscript.
mainList2 <- list(mainList, mainList) # First is Madrid, second is Valencia
# Operations are done on Madrid only
mainList2[[1]] %>%
transpose() %>%
map(function(x) {
flatten(x) %>% bind_rows(.id = 'id')
})
If you want to do this for both elements in mainList2, you can wrap the whole thing in map.
mainList2 %>% map(function(x) {
transpose(x) %>%
map(function(x) {
flatten(x) %>% bind_rows(.id = 'id')
})
})
You can combine the pairs in following way :
Map(rbind, unlist(mainList[[1]], recursive = FALSE),
unlist(mainList[[2]], recursive = FALSE))
Or using purrr you can also add an id column easily.
library(purrr)
map2(mainList[[1]] %>% flatten,
mainList[[2]] %>% flatten, dplyr::bind_rows, .id = 'id')
#[[1]]
# id col1 col2
#1 1 1 a
#2 1 2 b
#3 1 3 c
#4 2 8 r
#5 2 3 t
#6 2 4 q
#[[2]]
# id col1 col2
#1 1 3 e
#2 1 7 d
#3 1 4 g
#4 2 7 h
#5 2 5 w
#6 2 2 p
#[[3]]
# id col1 col2
#1 1 2 l
#2 1 7 o
#3 1 4 i
#4 2 9 x
#5 2 3 y
#6 2 6 z

How to erase all attributes?

I want to erase all attributes from data and applied this solution. However neither one_entry() (the original) nor my one_entry2() will work and I don't see why.
one_entry2 <- function(x) {
attr(x, "label") <- NULL
attr(x, "labels") <- NULL
}
> lapply(df1, one_entry2)
$`id`
NULL
$V1
NULL
$V2
NULL
$V3
NULL
How can we do this?
Data:
df1 <- setNames(data.frame(matrix(1:12, 3, 4)),
c("id", paste0("V", 1:3)))
attr(df1$V1, "labels") <- LETTERS[1:4]
attr(df1$V1, "label") <- letters[1:4]
attr(df1$V2, "labels") <- LETTERS[1:4]
attr(df1$V2, "label") <- letters[1:4]
attr(df1$V3, "labels") <- LETTERS[1:4]
attr(df1$V3, "label") <- letters[1:4]
> str(df1)
'data.frame': 3 obs. of 4 variables:
$ id: int 1 2 3
$ V1: int 4 5 6
..- attr(*, "labels")= chr "A" "B" "C" "D"
..- attr(*, "label")= chr "a" "b" "c" "d"
$ V2: int 7 8 9
..- attr(*, "labels")= chr "A" "B" "C" "D"
..- attr(*, "label")= chr "a" "b" "c" "d"
$ V3: int 10 11 12
..- attr(*, "labels")= chr "A" "B" "C" "D"
..- attr(*, "label")= chr "a" "b" "c" "d"
To remove all attributes, how about this
df1[] <- lapply(df1, function(x) { attributes(x) <- NULL; x })
str(df1)
#'data.frame': 3 obs. of 4 variables:
# $ id: int 1 2 3
# $ V1: int 4 5 6
# $ V2: int 7 8 9
# $ V3: int 10 11 12
Simplifying a bit #maurits-evers answer:
df1[] <- lapply(df1, as.vector)
str(df1)
#'data.frame': 3 obs. of 4 variables:
# $ id: int 1 2 3
# $ V1: int 4 5 6
# $ V2: int 7 8 9
# $ V3: int 10 11 12
The original answer is by Prof. Brian Ripley in this R-Help post.
In tidyverse world:
df1 <- df1 %>% mutate(across(everything(), as.vector))
With data.table
library(data.table)
# Assuming
# setDT(df1) # or
# df1 <- as.data.table(df1)
df1 <- df1[, lapply(.SD, as.vector)]
Provided all the columns are the same type (as in your example) you can do
df1[] = c(df1, recursive=TRUE)
The PKPDmisc package has a dplyr friendly way to do this:
library(PKPDmisc)
df %>% strip_attributes(c("label", "labels"))
The following is a simple solution (and will not convert a date class to a numeric):
df1 <- data.frame(df1)
For certain situations, a modified version of the answer by #maurits-evers may be useful.
Create a function to remove attributes.
remove_attributes <- function(x) {attributes(x) <- NULL; return(x)}
To remove attributes from one element in a list.
df1$V1 <- remove_attributes(df1$V1)
To remove attributes from all elements in a list.
df1 <- lapply(df1, remove_attributes)

How do I change column typings via for loop?

I have a df that where some columns are type character and I would like them to be integer values. I know I can switch typing using as.integer:
df$i <- as.integer(df$i)
But I would like to have a loop change a bunch of columns instead of having to run the command multiple times. Here's my code so far:
cols_to_change = c(37:49, 53:61)
for(i in cols_to_change)
{
class(df[, i]) <- 'integer'
}
I'm getting an error that a list object can't be converted to type 'integer', where am I wrong here? Is there an easier way to do this using one of the apply functions?
An easier way to do this would be to use dplyr::mutate_at:
df <- dplyr::mutate_at(df, c(37:49, 53:61), as.integer)
I think purrr::map or lapply offer fairly elegant solutions here (and just say no to for-loops in R if possible):
Let's make you a fake data frame with all character vectors:
> df <- data.frame(let1 = c('a', 'b', 'c'), num1 = c('1', '2', '3'),
let2 = c('d', 'e', 'f'), num2 = c('4', '5', '6'),
num3 = c('7', '8', '9'), let3 = c('g', 'h', 'i'),
stringsAsFactors = FALSE)
> str(df)
'data.frame': 3 obs. of 6 variables:
$ let1: chr "a" "b" "c"
$ num1: chr "1" "2" "3"
$ let2: chr "d" "e" "f"
$ num2: chr "4" "5" "6"
$ num3: chr "7" "8" "9"
$ let3: chr "g" "h" "i"
Then we want to change num1, num2, and num3 into integer vectors (columns 2, 4, and 5). For illustration, copy df to df2 and then use purrr::map. Here I refer to the columns by their column number, but you could also use the names.
> df2 <- df
> df2[, c(2,4,5)] <- purrr::map(df2[, c(2,4,5)], as.integer)
> str(df2)
'data.frame': 3 obs. of 6 variables:
$ let1: chr "a" "b" "c"
$ num1: int 1 2 3
$ let2: chr "d" "e" "f"
$ num2: int 4 5 6
$ num3: int 7 8 9
$ let3: chr "g" "h" "i"
If you don't want to load any other packages, lapply will work:
> df3 <- df
> df3[, c(2,4,5)] <- lapply(df3[, c(2,4,5)], as.integer)
> str(df3)
'data.frame': 3 obs. of 6 variables:
$ let1: chr "a" "b" "c"
$ num1: int 1 2 3
$ let2: chr "d" "e" "f"
$ num2: int 4 5 6
$ num3: int 7 8 9
$ let3: chr "g" "h" "i"

Unlisting nested lists and without loosing object classes

After a previous post regarding coercion of variables into their appropriate format, I realized that the problem is due to unlist():ing, which appears to kill off the object class of variables.
Consider a nested list (myList) of the following structure
> str(myList)
List of 2
$ lst1:List of 3
..$ var1: chr [1:4] "A" "B" "C" "D"
..$ var2: num [1:4] 1 2 3 4
..$ var3: Date[1:4], format: "1999-01-01" "2000-01-01" "2001-01-01" "2002-01-01"
$ lst2:List of 3
..$ var1: chr [1:4] "Q" "W" "E" "R"
..$ var2: num [1:4] 11 22 33 44
..$ var3: Date[1:4], format: "1999-01-02" "2000-01-03" "2001-01-04" "2002-01-05"
which contains different object types (character, numeric and Date) at the lowest level. I`ve been using
myNewLst <- lapply(myList, function(x) unlist(x,recursive=FALSE))
result <- do.call("rbind", myNewLst)
to get the desired structure of my resulting matrix. However, this yields a coercion into character for all variables, as seen here:
> str(result)
chr [1:2, 1:12] "A" "Q" "B" "W" "C" "E" "D" "R" "1" "11" "2" "22" "3" "33" "4" "44" "10592" "10593" "10957" "10959" "11323" "11326" ...
- attr(*, "dimnames")=List of 2
..$ : chr [1:2] "lst1" "lst2"
..$ : chr [1:12] "var11" "var12" "var13" "var14" ...
After reading a post on a similar issue, I've attempted to utilize do.call("c", x)
myNewLst <- lapply(myList, function(x) do.call("c", x))
result <- do.call("rbind", myNewLst)
Unfortunately, this also results in all variables being characters, as my first attempt. So my question is: How do I unlist a nested list without loosing the object class of my lower-level variables? Are there alternatives which will accomplish the desired result?
Reproducible code for myList:
myList <- list(
"lst1" = list(
"var1" = c("A","B","C","D"),
"var2" = c(1,2,3,4),
"var3" = c(as.Date('1999/01/01'),as.Date('2000/01/01'),as.Date('2001/01/01'),as.Date('2002/01/01'))
),
"lst2" = list(
"var1" = c("Q","W","E","R"),
"var2" = c(11,22,33,44),
"var3" = c(as.Date('1999/01/02'),as.Date('2000/01/03'),as.Date('2001/01/4'),as.Date('2002/01/05'))
)
)
You can use Reduce() or do.call() to be able to combine all of the to one dataframe. The code below should work
Reduce(rbind,lapply(myList,data.frame,stringsAsFactors=F))
var1 var2 var3
1 A 1 1999-01-01
2 B 2 2000-01-01
3 C 3 2001-01-01
4 D 4 2002-01-01
5 Q 11 1999-01-02
6 W 22 2000-01-03
7 E 33 2001-01-04
8 R 44 2002-01-05
Also the class is maintained:
mapply(class,Reduce(rbind,lapply(myList,data.frame,stringsAsFactors=F)))
var1 var2 var3
"character" "numeric" "Date"
If your goal is to convert this list of lists into a single data frame, the following code should work:
result <- data.frame(var1 = unlist(lapply(myList, function(e) e[1]), use.names = FALSE),
var2 = unlist(lapply(myList, function(e) e[2]), use.names = FALSE),
var3 = as.Date(unlist(lapply(myList, function(e) e[3]), use.names = FALSE), origin = "1970-01-01"))
This gives:
> result
var1 var2 var3
1 A 1 1999-01-01
2 B 2 2000-01-01
3 C 3 2001-01-01
4 D 4 2002-01-01
5 Q 11 1999-01-02
6 W 22 2000-01-03
7 E 33 2001-01-04
8 R 44 2002-01-05
Of course, you could use a for-loop to make the code more succinct if there are multiple variables in each list.

Why are my data.frame columns lists?

I have a data.frame
'data.frame': 4 obs. of 2 variables:
$ name:List of 4
..$ : chr "a"
..$ : chr "b"
..$ : chr "c"
..$ : chr "d"
$ tvd :List of 4
..$ : num 0.149
..$ : num 0.188
..$ : num 0.161
..$ : num 0.187
structure(list(name = list("a", "b", "c",
"d"), tvd = list(0.148831029536996, 0.187699857380692,
0.161428147003292, 0.18652668961466)), .Names = c("name",
"tvd"), row.names = c(NA, -4L), class = "data.frame")
It appears that as.data.frame(lapply(z,unlist)) converts it to the usual
'data.frame': 4 obs. of 2 variables:
$ name: Factor w/ 4 levels "a",..: 4 1 2 3
$ tvd : num 0.149 0.188 0.161 0.187
However, I wonder if I could do better.
I create my ugly data frame like this:
as.data.frame(do.call(rbind,lapply(my.list, function (m)
list(name = ...,
tvd = ...))))
I wonder if it is possible to modify this expressing so that it would produce the normal data table.
It looks like you're just trying to tear down your original data then re-assemble it? If so, here are a few cool things to look at. Assume df is your data.
A data.frame is just a list in disguise. To see this, compare df[[1]] to df$name in your data. [[ is used for list indexing, as well as $. So we are actually viewing a list item when we use df$name on a data frame.
> is.data.frame(df) # df is a data frame
# [1] TRUE
> is.list(df) # and it's also a list
# [1] TRUE
> x <- as.list(df) # as.list() can be more useful than unlist() sometimes
# take a look at x here, it's a bit long
> (y <- do.call(cbind, x)) # reassemble to matrix form
# name tvd
# [1,] "a" 0.148831
# [2,] "b" 0.1876999
# [3,] "c" 0.1614281
# [4,] "d" 0.1865267
> as.data.frame(y) # back to df
# name tvd
# 1 a 0.148831
# 2 b 0.1876999
# 3 c 0.1614281
# 4 d 0.1865267
I recommend doing
do.call(rbind,lapply(my.list, function (m)
data.frame(name = ...,
tvd = ...)))
rather than trying to convert a list of lists into a data.frame

Resources