【问题标题】:Tidying (v untidy) data in R / Shiny – difficulty dealing with lists在 R / Shiny 中整理(v untidy)数据——处理列表的困难
【发布时间】:2020-07-02 21:08:08
【问题描述】:

对 Shiny 来说相对较新,并试图解决我正在制作的使用 .gpx 文件的应用程序的一些问题。目前,在某些时候列表无法绑定在一起。调试它的很大一部分是我无法返回任何数据,即使在导入之后也是如此。文件是列表中列表的非常混乱的混合物等事实无济于事。为了使事情变得更加困难,我无法制作可重现的数据样本,因为 dput(head(df))) 返回所有列表,所以开头看起来像:

list(list(metadata = NULL, bounds = NULL, waypoints = NULL, tracks = list( list(Evening Ride= structure(list(lon = c(-0.12927)

还有一千多行 我已经检查了 dput() 输出并尝试将一个工作样本放在一起,但无法让它像数据一样工作。如果有人有让dput() 处理此类列表数据的功能,请告诉我。但是,str(df[[1]]),返回

List of 5
 $ metadata : NULL
 $ bounds   : NULL
 $ waypoints: NULL
 $ tracks   :List of 1
  ..$ :List of 1
  .. ..$ Evening Ride:'data.frame': 1746 obs. of  4 variables:
  .. .. ..$ lon : num [1:1746] -0.129 -0.129 -0.129 -0.129 -0.129 ...
  .. .. ..$ lat : num [1:1746] 51.5 51.5 51.5 51.5 51.5 ...
  .. .. ..$ ele : chr [1:1746] "25.3" "25.3" "25.2" "25.2" ...
  .. .. ..$ time: chr [1:1746] "2019-10-21T17:09:29Z" "2019-10-21T17:09:30Z" "2019-10-21T17:09:33Z" "2019-10-21T17:09:35Z" ...
 $ routes   : NULL

任何人,希望有人能发现我的代码出错的地方:)

所以,如果我想导入一个文件,我的代码如下所示(并且可行):


library(shiny)
library(tidyverse)
library(plotKML)
# Define UI for data upload app ----
ui <- fluidPage(

  # App title ----
  titlePanel("Uploading Files"),

  # Sidebar layout with input and output definitions ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    sidebarPanel(

      # Input: Select a file ----
      fileInput("file1", "Choose gpx Files",
                multiple = TRUE,
                accept = c(".gpx")),

      # Horizontal line ----
      tags$hr(),

      # Input: Select number of rows to display ----
      radioButtons("disp", "Display",
                   choices = c(Head = "head",
                               All = "all"),
                   selected = "head")

    ),

    # Main panel for displaying outputs ----
    mainPanel(

      # Output: Data file ----
      plotOutput("contents")

    )

  )
)

# Define server logic to read selected file ----
server <- function(input, output) {

  output$contents <- renderPlot({

    req(input$file1)

    tryCatch(
      {
        df <-
          plotKML::readGPX(input$file1$datapath)        

        list_row_frame <- data.frame(do.call(rbind, df)) %>% 
          as_data_frame()


        unnested_ride <- list_row_frame %>%
          mutate(ride = seq.int(nrow(list_row_frame))) %>% 
          unnest() %>% 
          unnest()


      }
    )
  })

}
shinyApp(ui, server)

这可以正常工作。但是,当我想更改代码以输入多个 .gpx 文件时,我遇到了错误'incompatible lengths: x, y'

我将只是服务器功能,因为 UI 是一样的。

server <- function(input, output) {

  output$contents <- renderTable({

    req(input$file1)

    tryCatch(
      {
        df <- 
          rbindlist(lapply(input$file1$datapath, plotKML::readGPX), 
                    use.names = TRUE, fill = T)

        list_row_frame <- data.frame(do.call(rbind, df)) %>% 
          as_data_frame()


        unnested_ride <- list_row_frame %>%
          unnest() %>% 
          unnest() 

        return(unnested_ride[[1]])

      }
    )
  })

}

这段代码有时确实很糟糕,但由于永远无法让 return(var) / return(var[[1]]) 返回任何东西,我几乎是盲目地这样做。我试图将 unnest() 更改为 unlist() 但随后我收到有关字符变量的错误。如果有人有任何建议,我将不胜感激。感谢您的帮助。

【问题讨论】:

  • 修改您的代码,以便您的函数单独处理每个文件。然后lapply 将该函数添加到输入文件列表。然后将bind_rowslapply返回的数据帧列表合并为单个数据帧。这紧凑、高效,可让您确定哪些文件有问题。
  • @Limey 感谢您的回答。为什么当前对lapplyreadGPX 的调用不单独处理每个文件?另外,你确定lapply 正在返回一个df,在我看来它是一个列表,带有一个$evening_ride 的df?如果它有所不同,我认为任何文件都没有问题,当运行不在 Shiny 中的(略有不同的)脚本时没有问题。

标签: r shiny


【解决方案1】:

使我之前的回答更加具体。我设法找到了一个与here 一起使用的 GPX 文件。我将使用 tibbles 而不是数据框,因为它们以更好的方式打印。恢复到 data.frames 应该没有任何影响。

读入数据文件,看看我们有什么:

library(plotKML)
library(tidyverse)

df <- readGPX("coasttocoast.gpx")
names(df)
[1] "metadata"  "bounds"    "waypoints" "tracks"    "routes"  
length(df$bounds)
[1] 0
length(df$tracks)
[1] 0
length(df$waypoints)
[1] 5
length(df$routes)
[1] 13
df$waypoints
         lon      lat       name  sym          type
1 -0.5312896 54.43030 C2C FINISH Flag LDP:c2c route
2 -3.6100834 54.48718  C2C START Flag LDP:c2c route
head(df$routes[[1]])
        lon      lat   name  cmt desc sym      type
1 -3.608879 54.48796 WP0101 <NA> <NA> Dot Waypoints
2 -3.607836 54.49090 WP0102 <NA> <NA> Dot Waypoints
3 -3.607223 54.49099 WP0103 <NA> <NA> Dot Waypoints
4 -3.609015 54.49142 WP0104 <NA> <NA> Dot Waypoints
5 -3.609934 54.49123 WP0105 <NA> <NA> Dot Waypoints
6 -3.610693 54.49090 WP0106 <NA> <NA> Dot Waypoints

所以我只能在航路点和路线上测试我的方法

编写convertToCommonFormat函数

它并不像看起来那么复杂。它只有六个语句长。我刚刚对其进行了格式化,因此您不必滚动。

convertToCommonFormat <- function(df=NA, x=NA, source, file, idx=NA) {
  if (is.na(x)) x <- df[[source]]
  if (is.null(x)) return(
                    tibble(
                      FileName=character(), 
                      Source=character(), 
                      Index=NA_integer_
                    )
                  )
  if (is.data.frame(x)) {
    # Using tibble rather than dataframe simply for ease of printing
    return(
      as_tibble(
        x %>% add_column(
                FileName=file, 
                Source=source, 
                Index=idx, 
                .before=1
              )
      )
    )
  } else if (is.list(x)) {
    return(
      bind_rows(
        lapply(
          1:length(x), 
          function(y) convertToCommonFormat(x=x[[y]], idx=y, file=file, source=source)
        )
      )
    )
  } else {
    print(x)
    stop(paste0("Don't know what to do with ", str(x)))
  }
}

测试一下:

convertToCommonFormat(df, source="tracks", file="coasttocoast.gpx")
# A tibble: 0 x 3
# … with 3 variables: FileName <chr>, Source <chr>, Index <int>
convertToCommonFormat(df, source="waypoints", file="coasttocoast.gpx")
# A tibble: 2 x 8
  FileName         Source    Index    lon   lat name       sym   type         
  <chr>            <chr>     <lgl>  <dbl> <dbl> <chr>      <chr> <chr>        
1 coasttocoast.gpx waypoints NA    -0.531  54.4 C2C FINISH Flag  LDP:c2c route
2 coasttocoast.gpx waypoints NA    -3.61   54.5 C2C START  Flag  LDP:c2c route
convertToCommonFormat(df, source="routes", file="coasttocoast.gpx")
# A tibble: 2,371 x 10
   FileName         Source Index   lon   lat name   cmt   desc  sym   type     
   <chr>            <chr>  <int> <dbl> <dbl> <chr>  <chr> <chr> <chr> <chr>    
 1 coasttocoast.gpx routes     1 -3.61  54.5 WP0101 NA    NA    Dot   Waypoints
 2 coasttocoast.gpx routes     1 -3.61  54.5 WP0102 NA    NA    Dot   Waypoints
 3 coasttocoast.gpx routes     1 -3.61  54.5 WP0103 NA    NA    Dot   Waypoints
 4 coasttocoast.gpx routes     1 -3.61  54.5 WP0104 NA    NA    Dot   Waypoints
 5 coasttocoast.gpx routes     1 -3.61  54.5 WP0105 NA    NA    Dot   Waypoints
 6 coasttocoast.gpx routes     1 -3.61  54.5 WP0106 NA    NA    Dot   Waypoints
 7 coasttocoast.gpx routes     1 -3.61  54.5 WP0107 NA    NA    Dot   Waypoints
 8 coasttocoast.gpx routes     1 -3.61  54.5 WP0108 NA    NA    Dot   Waypoints
 9 coasttocoast.gpx routes     1 -3.62  54.5 WP0109 NA    NA    Dot   Waypoints
10 coasttocoast.gpx routes     1 -3.62  54.5 WP0110 NA    NA    Dot   Waypoints
# … with 2,361 more rows

令人讨厌的是,routes 元素包含Waypoints 类型的记录,而waypoints 元素包含route 类型的记录。去图吧。

检查我们那里有所有 13 条路线:

# Prove that we've got all 13 routes in there
convertToCommonFormat(df, source="routes", file="coasttocoast.gpx") %>% 
  group_by(Index) %>% summarise(N=n(), .groups="drop")
# A tibble: 13 x 2
   Index     N
   <int> <int>
 1     1   194
 2     2    16
 3     3   186
 4     4   198
 5     5   162
 6     6   215
 7     7   137
 8     8   209
 9     9   157
10    10   272
11    11   133
12    12   247
13    13   245

编写processOneFile函数

processOneFile <- function(gpxFile) {
  df <- readGPX(gpxFile)
  tracks <- convertToCommonFormat(df, source="tracks", file=gpxFile)
  waypoints <- convertToCommonFormat(df, source="waypoints", file=gpxFile)
  routes <- convertToCommonFormat(df, source="routes", file=gpxFile)
  return(bind_rows(tracks, waypoints, routes))
}

并使用它

processOneFile("coasttocoast.gpx")
# A tibble: 2,373 x 10
   FileName         Source    Index    lon   lat name       sym   type          cmt   desc 
   <chr>            <chr>     <int>  <dbl> <dbl> <chr>      <chr> <chr>         <chr> <chr>
 1 coasttocoast.gpx waypoints    NA -0.531  54.4 C2C FINISH Flag  LDP:c2c route NA    NA   
 2 coasttocoast.gpx waypoints    NA -3.61   54.5 C2C START  Flag  LDP:c2c route NA    NA   
 3 coasttocoast.gpx routes        1 -3.61   54.5 WP0101     Dot   Waypoints     NA    NA   
 4 coasttocoast.gpx routes        1 -3.61   54.5 WP0102     Dot   Waypoints     NA    NA   
 5 coasttocoast.gpx routes        1 -3.61   54.5 WP0103     Dot   Waypoints     NA    NA   
 6 coasttocoast.gpx routes        1 -3.61   54.5 WP0104     Dot   Waypoints     NA    NA   
 7 coasttocoast.gpx routes        1 -3.61   54.5 WP0105     Dot   Waypoints     NA    NA   
 8 coasttocoast.gpx routes        1 -3.61   54.5 WP0106     Dot   Waypoints     NA    NA   
 9 coasttocoast.gpx routes        1 -3.61   54.5 WP0107     Dot   Waypoints     NA    NA   
10 coasttocoast.gpx routes        1 -3.61   54.5 WP0108     Dot   Waypoints     NA    NA   
# … with 2,363 more rows

【讨论】:

    【解决方案2】:

    从评论转到回答,因为我希望达到评论中的字符数限制。虽然我接受这不是正常意义上的完整答案......

    您现有代码中的lapply确实单独处理每个文件。我对此没有异议。如果我不清楚,请道歉。

    但是您会立即绑定它返回的(列表?)数据帧列表,然后再处理组合的数据帧。我想建议的是编写一个函数,它既可以读取 GPX 文件,又可以对原始数据进行必要的处理,以将单个原始数据文件转换为可以绑定在一起的格式。

    我不熟悉ploKML 包,但文档表明readGPX 的输出是一个数据帧列表,每个数据帧对应一个元数据、航点、轨迹和路线。 [来自在线文档:readGPX“从 *.gpx 文件中读取各种元素——元数据、航点、航迹和路线——并将它们转换为数据帧。”](我的重点:注意复数在数据帧上。)

    因此,撇开元数据不谈,假设航点、航迹和路线可以以通用格式存储在 data.frames 中似乎是合理的:可能包含Type(航点/航迹/路线)、Index 的列(以允许每种类型有多个元素)、DateTimeLongitudeLatitudeExtras(对于特定于类型的信息:例如,这可以是列表列或 df 列)。

    然后我的处理函数将调用readGPX 作为其第一行,然后是处理航点、轨迹和路线的一系列步骤,最后是将元素绑定到表示该文件的单个 df 的行。

    类似

    processOneFile <- function(gpxFile) {
      contents <- readGPX(gpxFile)
      waypointDF <- convertToCommonFormat(contents[["waypoints"]], type="waypoint")
      trackDF <- convertToCommonFormat(contents[["tracks"]], type="track")
      routeDF <- convertToCommonFormat(contents[["routes"]], type="route")
      return(bind_rows(waypointDF, trackDF, routeDF)) 
    }
    

    显然,您还必须编写convertToCommonFormat,如果您愿意,您可以在processOneFile 中调用lapply。在convertToCommonFormatprocessOneFile 中调试对print 的调用将有助于确定问题所在。

    然后,在您的主程序中,您使用lapply 来处理您的文件列表:

    allDataInOne <- bind_rows(lapply(myListOfFiles, processOneFile))
    

    理论上,我的工作流程和您的工作流程都应该在给出表现良好的输入数据时得到相同的结果。我认为我的优势在于,当输入数据文件表现不佳时,更容易找出问题所在。

    对不起,我不能给你一个具体的例子,但是由于你不能提供源数据(出于显而易见的原因)并且plotKML手册中的示例文件不可用,我不知道测试数据的简单来源。

    【讨论】:

    • 也许值得一提的是,您的方法的另一个好处是您可以为每个函数编写 unit tests 以帮助隔离和调试问题并使 Shiny 应用程序更加稳定。
    猜你喜欢
    • 2018-01-26
    • 1970-01-01
    • 2013-08-15
    • 1970-01-01
    • 2015-05-16
    • 2019-02-21
    • 1970-01-01
    • 2018-06-26
    • 1970-01-01
    相关资源
    最近更新 更多