在 data.table 中逐行应用返回列表/矩阵的函数

Posted

技术标签:

【中文标题】在 data.table 中逐行应用返回列表/矩阵的函数【英文标题】:Applying a function returning list/matrix row-wise in data.table 【发布时间】:2015-06-26 19:09:04 【问题描述】:

我正在尝试执行http://flowingdata.com/2011/05/11/how-to-map-connections-with-great-circles/ 中提到的步骤,但使用data.table。特别是那里列出的第 8 步。附上我的步骤和遇到的问题:

library(data.table)
library(maps)
library(geosphere)
airports <- as.data.table(read.csv("http://datasets.flowingdata.com/tuts/maparcs/airports.csv", header=TRUE))
flights <- as.data.table(read.csv("http://datasets.flowingdata.com/tuts/maparcs/flights.csv", header=TRUE, as.is=TRUE))

setnames(airports,c("airport1",names(airports)[2:7]))
setkey(flights,airport1)
setkey(airports,airport1)
ap <- merge(flights,airports)
setkey(ap,airport2)
setnames(airports,c("airport2",names(airports)[2:7]))
setkey(airports,airport2)
setkey(ap,airport2)
ap2 <- merge(ap,airports)
ap3 <- ap2[,.(airport1,airport2,airline,cnt,lat.x,long.x,lat.y,long.y)]
## ap3[,inter:=gcIntermediate(c(long.x,lat.x),c(long.y,lat.y),n=100,addStartEnd=TRUE),]  ## Error in .pointsToMatrix(p1) : Wrong length for a vector, should be 2
## ap3[,inter:=gcIntermediate(c(long.x,lat.x),c(long.y,lat.y),n=100,addStartEnd=TRUE),]  ## Error in .pointsToMatrix(p1) : Wrong length for a vector, should be 2
## 
## Tried some more stuff but no luck!
## fn <- function(lonx,latx,lony,laty) gcIntermediate(c(lonx,latx),c(lony,laty),n=100,addStartEnd=TRUE)
## ap3[,do.call(fn,.SD),.SDcols=5:8] ## Error in (function (lonx, latx, lony, laty)  : unused arguments (lat.x = c(35.21401111, 35.2140 ... snip ...

所以我搜索了 *** 并尝试了 [1] 和 [2] 中列出的步骤,但无法使其正常工作。我记得在某处(虽然现在找不到)读到 data.table 可以存储列表,但我不知道如何存储。另外,除了常见问题解答第 2.9 节中列出的内容之外,还有其他方法可以调试 j 中的函数吗?

[1]efficient row-wise operations on a data.table

[2]Applying a function to each row of a data.table

【问题讨论】:

很高兴这是可重现的,但您真的需要我们安装这些软件包吗?对于一个相当简单的问题(如何使用列表列)来说,这似乎很复杂。 没有。但是我不知道如何表达我遇到的问题,对不起。如果我能找出如何将不同长度/行的列表/矩阵(从函数返回......而不是手动创建)捕获到一个可以工作的 data.table 列中。 【参考方案1】:

这应该是真正的评论,但它不适合那里: 对于分别由 c(long.x,lat.x) 和 c(long.y,lat.y) 定义的每个 p1 和 p2,您有一个矩阵(或列表)(以下,我只关注矩阵) 并且该矩阵的维度取决于 n 和 addStartEnd 的值。例如,如果设置 n=1 和 addStartEnd=FALSE,它将返回一个 1×2 维矩阵,如果你设置 n=1 和 addStartEnd=TRUE,它将返回一个 3×2 维矩阵。现在,使用像您这样的 data.table 操作,您不能简单地附加值。我不是 data.table 专家,但我认为正确的方法是您必须进行逐行操作,然后使用 rbindlist.,例如,

apt<-setDT(ap3)

tt<-rbindlist(lapply(1:nrow(apt),function(i)cbind(apt[i,],gcIntermediate(apt[i,c("long.x","lat.x")],apt[i,c("long.y","lat.y")],n=100,addStartEnd=TRUE))))

> tt
        airport1 airport2 airline cnt    lat.x     long.x    lat.y    long.y        lon      lat
     1:      CLT      ABE     all  56 35.21401  -80.94313 40.65236  -75.4404  -80.94313 35.21401
     2:      CLT      ABE     all  56 35.21401  -80.94313 40.65236  -75.4404  -80.89245 35.26904
     3:      CLT      ABE     all  56 35.21401  -80.94313 40.65236  -75.4404  -80.84171 35.32405
     4:      CLT      ABE     all  56 35.21401  -80.94313 40.65236  -75.4404  -80.79090 35.37904
     5:      CLT      ABE     all  56 35.21401  -80.94313 40.65236  -75.4404  -80.74002 35.43401
    ---                                                                                         
510710:      PHX      YUM      YV 328 33.43417 -112.00806 32.65658 -114.6060 -114.50396 32.68840
510711:      PHX      YUM      YV 328 33.43417 -112.00806 32.65658 -114.6060 -114.52947 32.68045
510712:      PHX      YUM      YV 328 33.43417 -112.00806 32.65658 -114.6060 -114.55498 32.67250
510713:      PHX      YUM      YV 328 33.43417 -112.00806 32.65658 -114.6060 -114.58048 32.66454
510714:      PHX      YUM      YV 328 33.43417 -112.00806 32.65658 -114.6060 -114.60597 32.65658

根据@Frank 的建议:您可以仅使用 data.table 操作进行如下操作(其中 102 =100 (n)+ 2 (addStartEnd=TRUE))

ap3[,gcIntermediate(c(long.x,lat.x),c(long.y,lat.y),n=100,addStartEnd=TRUE),by=1:nrow(ap3)][,list(lon=head(V1,102),lat=tail(V1,102)),by=nrow]
        nrow        lon      lat
     1:    1  -80.94313 35.21401
     2:    1  -80.89245 35.26904
     3:    1  -80.84171 35.32405
     4:    1  -80.79090 35.37904
     5:    1  -80.74002 35.43401
    ---                         
510710: 5007 -114.50396 32.68840
510711: 5007 -114.52947 32.68045
510712: 5007 -114.55498 32.67250
510713: 5007 -114.58048 32.66454
510714: 5007 -114.60597 32.65658

【讨论】:

谢谢@Frank。现已更正。 代码看起来是正确的,但是您可能需要注意 setDT 是通过引用工作的,所以您实际上已经修改了 ap3(因此不需要将结果分配给新的目的)。此外,您的代码似乎可以采用 apt[,some_thing,by=1:nrow(apt)] 的形式,这对于 data.table 来说比 rbindlist+lapply 方法更惯用。 @Frank:它创建了一个包含 1021428 行的列。你知道如何分成两列吗? 可能是as.list(myfun(...))?我不确定。 nrow 是第一次操作生成的新列。然后在第二个操作中,我们通过每个 nrow(1,2,...) 选择 col V1 的前 102 行,然后将其分配给 lon 列,将 col V1 的最后 102 行通过 nrow 分配给 lat 列。一旦您省略了第二个操作并仅看到第一个操作的输出,第二个操作就会很清楚。【参考方案2】:

假设你有一个函数返回一个未知大小的矩阵。您可以在带有列表列的data.table 中分配结果:

# example data
set.seed(42)
DT <- data.table(id=1:3)[,.(v=sample(letters,sample(5,1))),by=id]

# example function
myfun = function(x) matrix(x, ncol= if(length(x)%%2) 1 else 2 )

# usage 
res <- DT[,.(vlist = list(myfun(v))),by=id]
#    id     vlist
# 1:  1 y,h,t,o,l
# 2:  2   d,q,y,k
# 3:  3   y,g,l,v

这可能看起来不像一列矩阵,但你可以看到它是:

str(res$vlist)
# List of 3
#  $ : chr [1:5, 1] "y" "h" "t" "o" ...
#  $ : chr [1:2, 1:2] "d" "q" "y" "k"
#  $ : chr [1:2, 1:2] "y" "g" "l" "v"

res$vlist[[2]]
#      [,1] [,2]
# [1,] "d"  "y" 
# [2,] "q"  "k" 

(我不确定这是否是您所追求的,因为我没有浏览链接的博客文章。)

【讨论】:

谢谢,弗兰克。这正是我一直在寻找的。我不知道需要.() 才能完成这项工作,但显然确实如此。我在没有.() 的情况下尝试这个,这就是我不断收到奇怪错误的地方。

以上是关于在 data.table 中逐行应用返回列表/矩阵的函数的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ASP.NET Core 3.1 中逐行迭代列表

在Python中逐行读取多行字符串

如何禁用 .onDelete- 或如何在列表中逐行使用 .deleteDisabled?

从文本文件中逐行提取数据并将其存储在python的列表中[重复]

在一个非常大的文件中逐行读取特定的行

在 VBA 中逐行读取/解析文本文件