简体   繁体   English

如何让R循环更快?

[英]How to make R loop faster?

I'm trying to convert a nested json file to a data frame in R using the following function: 我正在尝试使用以下函数将嵌套的json文件转换为R中的数据框:

rf1 <- function(data) {
master <-
data.frame(
  id = character(0),
  awardAmount = character(0),
  awardStatus = character(0),
  tenderAmount = character(0)
)
 for (i in 1:nrow(data)) {
 temp1 <- unlist(data$data$awards[[i]]$status)
 length <- length(temp1)
 temp2 <- rep(data$data$id[i], length)
 temp3 <- rep(data$data$value$amount[[i]], length)
 temp4 <- unlist(data$data$awards[[i]]$value[[1]])
 tempDF <-
   data.frame(id = temp2, 
              awardAmount = temp4, 
              awardStatus = temp1,
              tenderAmount = temp3)
   master <- rbind(master, tempDF)
  }
 return(master)
}

Here's an example of the json files I'm using: 这是我正在使用的json文件的示例:

{
    "data" : {
        "id" : "3f066cdd81cf4944b42230ed56a35bce",
        "awards" : [
            {
                "status" : "unsuccessful",
                "value" : {
                    "amount" : 76
                }
            },
            {
                "status" : "active",
                "value" : {
                    "amount" : 41220
                }
            }
        ],
        "value" : {
            "amount" : 48000
        }
    }
},
{
    "data" : {
        "id" : "9507162e6ee24cef8e0ea75d46a81a30",
        "awards" : [
            {
                "status" : "active",
                "value" : {
                    "amount" : 2650
                }
            }
        ],
        "value" : {
            "amount" : 2650
        }
    }
},
{
    "data" : {
        "id" : "a516ac43240c4ec689f3392cf0c17575",
        "awards" : [
            {
                "status" : "active",
                "value" : {
                    "amount" : 2620
                }
            }
        ],
        "value" : {
            "amount" : 2650
        }
    }
}

As you can see, the three observations have different number of awards (the first observation has two awards while the other two have only one). 正如您所看到的,这三个观察的奖项数量不同(第一个观察有两个奖项,而另外两个只有一个奖励)。 Since I'm looking for a table-view data frame, I'm filling the empty cells with repetitive information such as data$id and data$value$amount . 由于我正在寻找一个表视图数据帧,我正在填充空单元格中的重复信息,例如data$iddata$value$amount

The json file has approximately 100,000 observations, so it takes forever to return a data frame (I've been waiting for more than 30 minutes and still no result). json文件有大约100,000个观察值,因此返回一个数据帧需要永远(我已经等了30多分钟但仍然没有结果)。 I think that there might be a way to run all the temp lines in parallel, which should save a lot of time, but I'm not sure how to implement that in my code. 我认为可能有一种方法可以并行运行所有temp行,这可以节省大量时间,但我不知道如何在我的代码中实现它。

To give you a sense of the output I'm looking for, I limited my function to for (i in 1:3) , which produced the following data frame. 为了让您了解我正在寻找的输出,我将我的功能限制for (i in 1:3) ,这产生了以下数据帧。 My question is how to do the same thing but for 100,000 observations. 我的问题是如何做同样的事情,但是对于100,000次观察。 Note, the json example corresponds to the sample output. 注意,json示例对应于示例输出。

Desired output: 期望的输出:

样本输出

This is by no means elegant, but it appears to work: 这绝不是优雅的,但似乎有效:

library(jsonlite)
library(purrr)
library(dplyr)

json_data <- '[{"data":{"id":"3f066cdd81cf4944b42230ed56a35bce","awards":[{"status":"unsuccessful","value":{"amount":76}},{"status":"active","value":{"amount":41220}}],"value":{"amount":48000}}},{"data":{"id":"9507162e6ee24cef8e0ea75d46a81a30","awards":[{"status":"active","value":{"amount":2650}}],"value":{"amount":2650}}},{"data":{"id":"a516ac43240c4ec689f3392cf0c17575","awards":[{"status":"active","value":{"amount":2620}}],"value":{"amount":2650}}}] '

# parse original JSON records
parsed_json_data <- fromJSON(json_data)$data

# extract awards data, un-nest the nested parts, and re-assemble awards into a data frame for each id
awards <- map2(.x = parsed_json_data$id, 
               .y = parsed_json_data$awards,
               .f = function(x, y) bind_cols(data.frame('id' = rep(x, nrow(y)), stringsAsFactors = FALSE), as.data.frame(as.list(y))))

# bind together the data frames over all ids
awards <- 
  bind_rows(awards) %>% 
  rename(awards_status = status, awards_amount = amount)

# remove awards data from original parsed data
parsed_json_data$awards <- NULL

# un-nest the remaining data structures
parsed_json_data <- as.data.frame(as.list(parsed_json_data), stringsAsFactors = FALSE)

# join higher-level data with awards data (in denormalisation process)
final_data_frame <- inner_join(parsed_json_data, awards, by = 'id')

final_data_frame
#   id                                amount  awards_status  awards_amount
# 1 3f066cdd81cf4944b42230ed56a35bce  48000   unsuccessful   76
# 2 3f066cdd81cf4944b42230ed56a35bce  48000         active   41220
# 3 9507162e6ee24cef8e0ea75d46a81a30   2650         active   2650
# 4 a516ac43240c4ec689f3392cf0c17575   2650         active   2620

Another approach is to remove the work form R and re-construct your mongodb query. 另一种方法是删除工作表单R并重新构建您的mongodb查询。

If this is your data in mongodb 如果这是您在mongodb中的数据

在此输入图像描述

In the mongo shell you can write a query along the lines of 在mongo shell中,您可以按行编写查询

db.json.aggregate([  
        { "$unwind" : "$data.awards"},
        { "$group" : { 
            "_id" :  {"id" : "$data.id", "status" : "$data.awards.status"}, 
            "awardAmount" : { "$sum" : "$data.awards.value.amount" },
            "tenderAmount" : { "$sum" : "$data.value.amount" }
            }
        },
        { "$project" : { 
              "id" : "$_id.id", 
              "status" : "$_id.status", 
              "awardAmount" : "$awardAmount", 
              "tenderAmount" : "$tenderAmount", 
              "_id" : 0}  } 
   ])

(note: I'm not a mongodb expert, so there may be a slightly more concise way of writing this) (注意:我不是mongodb专家,所以可能会有更简洁的方式来写这个)

Which you can also use in R 你也可以在R中使用它

library(mongolite)
mongo <- mongo(collection = "json", db = "test")

qry <- '[  
                    { "$unwind" : "$data.awards"},
                    { "$group" : { 
                                "_id" :  {"id" : "$data.id", "status" : "$data.awards.status"}, 
                                "awardAmount" : { "$sum" : "$data.awards.value.amount" },
                                "tenderAmount" : { "$sum" : "$data.value.amount" }
                            }
                    },
                    { "$project" : {  
                                "id" : "$_id.id", 
                                "status" : "$_id.status", 
                                "awardAmount" : "$awardAmount", 
                                "tenderAmount" : "$tenderAmount",
                                "_id" : 0}  
                            } 
                    ]'

df <- mongo$aggregate(pipeline = qry)
df
#   awardAmount tenderAmount                               id       status
# 1        2620         2650 a516ac43240c4ec689f3392cf0c17575       active
# 2       41220        48000 3f066cdd81cf4944b42230ed56a35bce       active
# 3        2650         2650 9507162e6ee24cef8e0ea75d46a81a30       active
# 4          76        48000 3f066cdd81cf4944b42230ed56a35bce unsuccessful

This may be most the unsophisticated approach there is. 这可能是最简单的方法。 It doesn't use JSON parsing, but utilizes a bunch of regex's 它不使用JSON解析,而是使用一堆正则表达式

But yeah, I agree with SymbolixAU that doing it in the mongo query is the way to go. 但是,我同意SymbolixAU,在mongo查询中这样做是可行的。

# load json file ("file.json") just as a single string / single-element character vector 
jsonAsString <- readChar("file.json", file.info("file.json")$size)

# chunk the tenders
dataChunks <- unlist(strsplit(jsonAsString, '"data" : \\{'))
dataChunks <- dataChunks[grepl("id", dataChunks)]     # this removes the unnecessary header

# get the award subchunks
awardSubChunks <- gsub('.*("awards".*?}.*?}.*?]).*', "\\1", dataChunks)

  # scrape status values out of the award subchunks
statusIndex <- gregexpr('(?<="status" : ")([[:alnum:]]*)', awardSubChunks, perl = T)
status <- unlist(regmatches(awardSubChunks, statusIndex))

  # scrape bidAmount value out of the award subchunks
bidAmountIndex <- gregexpr('(?<="amount" : )([[:alnum:]]*)', awardSubChunks, perl = T)
bidAmount <- unlist(regmatches(awardSubChunks, bidAmountIndex))

# get the id and tender by removing the award subchunks
idTenderAmount <- gsub('"awards".*?}.*?}.*?]', "", dataChunks)

  # scrape id and tenderAmount values
id <- gsub('.*"id" : "([[:alnum:]]*)".*', "\\1", idTenderAmount)
tenderAmount <- gsub('.*"amount" : ([[:alnum:]]*).*', "\\1", idTenderAmount)

# find the number of bids per Id in order to find number of times id and tenderAmount needs to be repeated
numBidsPerId <- gregexpr("value", awardSubChunks)
numBidsTotal <- sapply(numBidsPerId, length)

# putting things together
df <- data.frame(id = rep(id, numBidsTotal),
                 tenderAmount = rep(tenderAmount, numBidsTotal),
                 status = status,
                 bidAmount = bidAmount)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM