Reputation: 10441
I know questions of this nature (convert lists into dataframes) have been asked before, however I'm running into a particular issue with a nested list of lists that I'd like to convert into a dataframe. The data I'm getting is from an API call in R, hence why I am dealing with this nested list of lists structure. Here's a small example of the API return object I am working with (5 games of sports data):
dput(soccer_data)
list(structure(list(id = 1603158L, league_id = 779L, season_id = 914L,
stage_id = 1810L, round_id = 29156L, group_id = NULL, aggregate_id = NULL,
venue_id = 139L, referee_id = 656L, localteam_id = 607L,
visitorteam_id = 3639L, weather_report = NULL, commentaries = TRUE,
attendance = NULL, pitch = NULL, winning_odds_calculated = FALSE,
formations = structure(list(localteam_formation = "4-2-3-1",
visitorteam_formation = "4-1-4-1"), .Names = c("localteam_formation",
"visitorteam_formation")), scores = structure(list(localteam_score = 5L,
visitorteam_score = 1L, localteam_pen_score = 0L, visitorteam_pen_score = 0L,
ht_score = "1-0", ft_score = "5-1", et_score = NULL), .Names = c("localteam_score",
"visitorteam_score", "localteam_pen_score", "visitorteam_pen_score",
"ht_score", "ft_score", "et_score")), time = structure(list(
status = "FT", starting_at = structure(list(date_time = "2017-03-04 05:30:00",
date = "2017-03-04", time = "05:30:00", timestamp = 1488605400L,
timezone = "UTC"), .Names = c("date_time", "date",
"time", "timestamp", "timezone")), minute = 90L, extra_minute = NULL,
injury_time = NULL), .Names = c("status", "starting_at",
"minute", "extra_minute", "injury_time")), coaches = structure(list(
localteam_coach_id = 429924L, visitorteam_coach_id = 429940L), .Names = c("localteam_coach_id",
"visitorteam_coach_id")), standings = structure(list(localteam_position = NULL,
visitorteam_position = NULL), .Names = c("localteam_position",
"visitorteam_position")), deleted = FALSE), .Names = c("id",
"league_id", "season_id", "stage_id", "round_id", "group_id",
"aggregate_id", "venue_id", "referee_id", "localteam_id", "visitorteam_id",
"weather_report", "commentaries", "attendance", "pitch", "winning_odds_calculated",
"formations", "scores", "time", "coaches", "standings", "deleted"
)), structure(list(id = 1603159L, league_id = 779L, season_id = 914L,
stage_id = 1810L, round_id = 29156L, group_id = NULL, aggregate_id = NULL,
venue_id = 113L, referee_id = 3614L, localteam_id = 577L,
visitorteam_id = 75L, weather_report = NULL, commentaries = FALSE,
attendance = NULL, pitch = NULL, winning_odds_calculated = FALSE,
formations = structure(list(localteam_formation = "4-2-3-1",
visitorteam_formation = "4-2-3-1"), .Names = c("localteam_formation",
"visitorteam_formation")), scores = structure(list(localteam_score = 1L,
visitorteam_score = 1L, localteam_pen_score = 0L, visitorteam_pen_score = 0L,
ht_score = "1-0", ft_score = "1-1", et_score = NULL), .Names = c("localteam_score",
"visitorteam_score", "localteam_pen_score", "visitorteam_pen_score",
"ht_score", "ft_score", "et_score")), time = structure(list(
status = "FT", starting_at = structure(list(date_time = "2017-03-04 22:00:00",
date = "2017-03-04", time = "22:00:00", timestamp = 1488664800L,
timezone = "UTC"), .Names = c("date_time", "date",
"time", "timestamp", "timezone")), minute = 90L, extra_minute = NULL,
injury_time = NULL), .Names = c("status", "starting_at",
"minute", "extra_minute", "injury_time")), coaches = structure(list(
localteam_coach_id = 455860L, visitorteam_coach_id = 176760L), .Names = c("localteam_coach_id",
"visitorteam_coach_id")), standings = structure(list(localteam_position = NULL,
visitorteam_position = NULL), .Names = c("localteam_position",
"visitorteam_position")), deleted = FALSE), .Names = c("id",
"league_id", "season_id", "stage_id", "round_id", "group_id",
"aggregate_id", "venue_id", "referee_id", "localteam_id", "visitorteam_id",
"weather_report", "commentaries", "attendance", "pitch", "winning_odds_calculated",
"formations", "scores", "time", "coaches", "standings", "deleted"
)), structure(list(id = 1603160L, league_id = 779L, season_id = 914L,
stage_id = 1810L, round_id = 29156L, group_id = NULL, aggregate_id = NULL,
venue_id = 28L, referee_id = 555L, localteam_id = 413L, visitorteam_id = 583L,
weather_report = NULL, commentaries = FALSE, attendance = 23554L,
pitch = NULL, winning_odds_calculated = FALSE, formations = structure(list(
localteam_formation = "4-4-1-1", visitorteam_formation = "4-4-2"), .Names = c("localteam_formation",
"visitorteam_formation")), scores = structure(list(localteam_score = 1L,
visitorteam_score = 2L, localteam_pen_score = 0L, visitorteam_pen_score = 0L,
ht_score = "0-0", ft_score = "1-2", et_score = NULL), .Names = c("localteam_score",
"visitorteam_score", "localteam_pen_score", "visitorteam_pen_score",
"ht_score", "ft_score", "et_score")), time = structure(list(
status = "FT", starting_at = structure(list(date_time = "2017-03-05 00:00:00",
date = "2017-03-05", time = "00:00:00", timestamp = 1488672000L,
timezone = "UTC"), .Names = c("date_time", "date",
"time", "timestamp", "timezone")), minute = 90L, extra_minute = NULL,
injury_time = NULL), .Names = c("status", "starting_at",
"minute", "extra_minute", "injury_time")), coaches = structure(list(
localteam_coach_id = 429914L, visitorteam_coach_id = 429917L), .Names = c("localteam_coach_id",
"visitorteam_coach_id")), standings = structure(list(localteam_position = NULL,
visitorteam_position = NULL), .Names = c("localteam_position",
"visitorteam_position")), deleted = FALSE), .Names = c("id",
"league_id", "season_id", "stage_id", "round_id", "group_id",
"aggregate_id", "venue_id", "referee_id", "localteam_id", "visitorteam_id",
"weather_report", "commentaries", "attendance", "pitch", "winning_odds_calculated",
"formations", "scores", "time", "coaches", "standings", "deleted"
)), structure(list(id = 1603161L, league_id = 779L, season_id = 914L,
stage_id = 1810L, round_id = 29156L, group_id = NULL, aggregate_id = NULL,
venue_id = 411L, referee_id = 274L, localteam_id = 1062L,
visitorteam_id = 111L, weather_report = NULL, commentaries = FALSE,
attendance = NULL, pitch = NULL, winning_odds_calculated = FALSE,
formations = structure(list(localteam_formation = "4-2-3-1",
visitorteam_formation = "3-5-2"), .Names = c("localteam_formation",
"visitorteam_formation")), scores = structure(list(localteam_score = 0L,
visitorteam_score = 0L, localteam_pen_score = 0L, visitorteam_pen_score = 0L,
ht_score = "0-0", ft_score = "0-0", et_score = NULL), .Names = c("localteam_score",
"visitorteam_score", "localteam_pen_score", "visitorteam_pen_score",
"ht_score", "ft_score", "et_score")), time = structure(list(
status = "FT", starting_at = structure(list(date_time = "2017-03-05 00:30:00",
date = "2017-03-05", time = "00:30:00", timestamp = 1488673800L,
timezone = "UTC"), .Names = c("date_time", "date",
"time", "timestamp", "timezone")), minute = 90L, extra_minute = NULL,
injury_time = NULL), .Names = c("status", "starting_at",
"minute", "extra_minute", "injury_time")), coaches = structure(list(
localteam_coach_id = 456638L, visitorteam_coach_id = 516577L), .Names = c("localteam_coach_id",
"visitorteam_coach_id")), standings = structure(list(localteam_position = NULL,
visitorteam_position = NULL), .Names = c("localteam_position",
"visitorteam_position")), deleted = FALSE), .Names = c("id",
"league_id", "season_id", "stage_id", "round_id", "group_id",
"aggregate_id", "venue_id", "referee_id", "localteam_id", "visitorteam_id",
"weather_report", "commentaries", "attendance", "pitch", "winning_odds_calculated",
"formations", "scores", "time", "coaches", "standings", "deleted"
)), structure(list(id = 1603162L, league_id = 779L, season_id = 914L,
stage_id = 1810L, round_id = 29157L, group_id = NULL, aggregate_id = NULL,
venue_id = 11573L, referee_id = 370L, localteam_id = 179L,
visitorteam_id = 641L, weather_report = NULL, commentaries = FALSE,
attendance = NULL, pitch = NULL, winning_odds_calculated = FALSE,
formations = structure(list(localteam_formation = "4-2-3-1",
visitorteam_formation = "4-3-1-2"), .Names = c("localteam_formation",
"visitorteam_formation")), scores = structure(list(localteam_score = 1L,
visitorteam_score = 0L, localteam_pen_score = 0L, visitorteam_pen_score = 0L,
ht_score = "0-0", ft_score = "1-0", et_score = NULL), .Names = c("localteam_score",
"visitorteam_score", "localteam_pen_score", "visitorteam_pen_score",
"ht_score", "ft_score", "et_score")), time = structure(list(
status = "FT", starting_at = structure(list(date_time = "2017-03-05 02:00:00",
date = "2017-03-05", time = "02:00:00", timestamp = 1488679200L,
timezone = "UTC"), .Names = c("date_time", "date",
"time", "timestamp", "timezone")), minute = 90L, extra_minute = NULL,
injury_time = NULL), .Names = c("status", "starting_at",
"minute", "extra_minute", "injury_time")), coaches = structure(list(
localteam_coach_id = 524071L, visitorteam_coach_id = 261458L), .Names = c("localteam_coach_id",
"visitorteam_coach_id")), standings = structure(list(localteam_position = NULL,
visitorteam_position = NULL), .Names = c("localteam_position",
"visitorteam_position")), deleted = FALSE), .Names = c("id",
"league_id", "season_id", "stage_id", "round_id", "group_id",
"aggregate_id", "venue_id", "referee_id", "localteam_id", "visitorteam_id",
"weather_report", "commentaries", "attendance", "pitch", "winning_odds_calculated",
"formations", "scores", "time", "coaches", "standings", "deleted"
)))
soccer_data has 5 games of MLS soccer data, and here is what I am currently doing to convert this into a dataframe:
# grab the "scores" info from the nested list $scores (from each game)
season_scores <- data.frame()
for(i in 1:length(soccer_data)) {
game_scores <- as.data.frame(t(unlist(soccer_data[[i]]$scores)), stringsAsFactors = FALSE)
game_scores$date <- as.Date(soccer_data[[i]]$time$starting_at$date)
season_scores <- rbind.fill(season_scores, game_scores)
}
season_scores <- season_scores %>% readr::type_convert()
# create df of the game scores, add the season scores, and drop the bad cols
season_boxscores <- as.data.frame(do.call(rbind, soccer_data), stringsAsFactors = FALSE) %>%
dplyr::select(-one_of(c('scores', 'group_id', 'aggregate_id', 'time', 'standings'))) %>%
cbind(season_scores) %>%
readr::type_convert()
Unfortunately, the issue with this approach is that the very last type_convert() function call doesn't do what I'd like, and the resulting season_boxscores dataframe has columns whose classes are mostly of class == list.
# check yourself
sapply(season_boxscores, class)
My questions then are:
Thanks in advance!
EDIT: It would be particularly nice if all of the nested lists (in this case, soccer_data has a few: formations, scores, time, coaches, standings) sort of unnested themselves, in the same way that I unnested them in the for loop for scores.
EDIT 2: Sorry for sharing such a large list object for only 5 games. In a list of lists, or big nested object like this, I actually do not know how to remove the same item from each nested list, which I would've done for this post. (ie remove league_id, round_id, etc. from each of soccer_data[[i]]). If anyone knows how to do that either, would be great to know!
EDIT 3: because soccer_data isnt simply a list of lists, but rather a list of list of lists (with other, non-list objects in each list of lists), none of the solutions here - Force list of lists into dataframe - work on soccer_data.
Upvotes: 3
Views: 648
Reputation: 50718
How about the following base R only approach (using unlist
):
Collapse list of list of list to list
of char
vectors:
# Collapse list of list of list to list of character vectors
lst <- lapply(soccer_data, unlist);
Make sure that all list entries have the same keys. For example, only list
entry 3 of your sample data has key attendance
.
# Make sure that all list entries have values for the same keys
keys <- unique(unlist(lapply(lst, names)));
Fill missing key entries with NA
# Fill missing entries with NULL
lst <- lapply(lst, function(x) x[match(keys, names(x))]);
rbind
into data.frame
:
# Combind in dataframe
df <- do.call(rbind.data.frame, lst);
colnames(df) <- keys;
df;
#id league_id season_id stage_id round_id venue_id referee_id
#1 1603158 779 914 1810 29156 139 656
#2 1603159 779 914 1810 29156 113 3614
#3 1603160 779 914 1810 29156 28 555
#4 1603161 779 914 1810 29156 411 274
#5 1603162 779 914 1810 29157 11573 370
#localteam_id visitorteam_id commentaries winning_odds_calculated
#1 607 3639 TRUE FALSE
#2 577 75 FALSE FALSE
#3 413 583 FALSE FALSE
#4 1062 111 FALSE FALSE
#5 179 641 FALSE FALSE
#formations.localteam_formation formations.visitorteam_formation
#1 4-2-3-1 4-1-4-1
#2 4-2-3-1 4-2-3-1
#3 4-4-1-1 4-4-2
#4 4-2-3-1 3-5-2
#5 4-2-3-1 4-3-1-2
#scores.localteam_score scores.visitorteam_score scores.localteam_pen_score
#1 5 1 0
#2 1 1 0
#3 1 2 0
#4 0 0 0
#5 1 0 0
#scores.visitorteam_pen_score scores.ht_score scores.ft_score time.status
#1 0 1-0 5-1 FT
#2 0 1-0 1-1 FT
#3 0 0-0 1-2 FT
#4 0 0-0 0-0 FT
#5 0 0-0 1-0 FT
#time.starting_at.date_time time.starting_at.date time.starting_at.time
#1 2017-03-04 05:30:00 2017-03-04 05:30:00
#2 2017-03-04 22:00:00 2017-03-04 22:00:00
#3 2017-03-05 00:00:00 2017-03-05 00:00:00
#4 2017-03-05 00:30:00 2017-03-05 00:30:00
#5 2017-03-05 02:00:00 2017-03-05 02:00:00
#time.starting_at.timestamp time.starting_at.timezone time.minute
#1 1488605400 UTC 90
#2 1488664800 UTC 90
#3 1488672000 UTC 90
#4 1488673800 UTC 90
#5 1488679200 UTC 90
#coaches.localteam_coach_id coaches.visitorteam_coach_id deleted attendance
#1 429924 429940 FALSE <NA>
#2 455860 176760 FALSE <NA>
#3 429914 429917 FALSE 23554
#4 456638 516577 FALSE <NA>
#5 524071 261458 FALSE <NA>
If you remove all the excess text/explanations, this is quite short.
Unfortunately, column types are lost as a result of unlist
. You can convert factors
back to numeric
in the following way:
# Smart-convert to numeric
is.num <- apply(df, 2, function(x) {
x <- x[!is.na(x)];
all(suppressWarnings(!is.na(as.numeric(as.character(x)))));
})
df[, is.num] <- apply(df[, is.num], 2, function(x) as.numeric(as.character(x)));
It's a bit messy but works.
Upvotes: 1
Reputation: 1648
I'm still trying to learn this stuff too. I tested a million things and this was the simplest I could come up with:
library(tidyverse)
soccer_data %>%
map(unlist) %>%
map(t) %>%
map(as_tibble) %>%
bind_rows()
The idea: take your list soccer_data
, map unlist
to every element (so it unlists at the second level, which means it keeps all the games in separate elements of the top-most list). Then use map transpose t
to turn the list to a thing that looks like a row, then convert it to a tibble
, then bind_rows
them altogether.
The result:
# A tibble: 5 x 30
id league_id season_id stage_id round_id venue_id referee_id localteam_id
<chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 1603158 779 914 1810 29156 139 656 607
2 1603159 779 914 1810 29156 113 3614 577
3 1603160 779 914 1810 29156 28 555 413
4 1603161 779 914 1810 29156 411 274 1062
5 1603162 779 914 1810 29157 11573 370 179
# ... with 22 more variables: visitorteam_id <chr>, commentaries <chr>,
# winning_odds_calculated <chr>, formations.localteam_formation <chr>,
# formations.visitorteam_formation <chr>, scores.localteam_score <chr>,
# scores.visitorteam_score <chr>, scores.localteam_pen_score <chr>,
# scores.visitorteam_pen_score <chr>, scores.ht_score <chr>, scores.ft_score <chr>,
# time.status <chr>, time.starting_at.date_time <chr>, time.starting_at.date <chr>,
# time.starting_at.time <chr>, time.starting_at.timestamp <chr>,
# time.starting_at.timezone <chr>, time.minute <chr>,
# coaches.localteam_coach_id <chr>, coaches.visitorteam_coach_id <chr>,
# deleted <chr>, attendance <chr>
Does that look right? Good luck!
Upvotes: 3