中在我的脑海中。不太重要,因为我对data.table
相当熟悉,但我知道对于刚接触这两个工具的用户来说,这将是一个很大的因素。我希望避免关于哪一个更直观的争论,因为这与我从已经熟悉data.table
的人的角度提出的特定问题无关。我还想避免讨论“更直观”如何导致更快的分析(当然是正确的,但这里不是我最感兴趣的)。
我想知道的是:
最近的一个SO问题让我对此有了更多的思考,因为在此之前,我并不认为dplyr
能够提供比data.table
中已经提供的功能更多的东西。以下是DPLYR
解决方案(Q结尾的数据):
dat %.%
group_by(name, job) %.%
filter(job != "Boss" | year == min(year)) %.%
mutate(cumu_job2 = cumsum(job2))
这比我攻击data.table
解决方案要好得多。也就是说,好的data.table
解决方案也是非常好的(谢谢Jean-Robert、Arun,这里请注意,我更喜欢单一语句,而不是最优的解决方案):
setDT(dat)[,
.SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)],
by=list(id, job)
]
后者的语法可能看起来非常深奥,但如果您习惯data.table
(即不使用一些更深奥的技巧),它实际上非常简单。
理想情况下,我希望看到的是dplyr
或data.table
方式的一些好的示例,它会更简洁,或者性能更好。
dplyr
不允许返回任意行数的分组操作(根据Eddi的问题,注意:这似乎将在dplyr 0.5中实现,而且,@beginner在@eddi问题的答案中显示了使用do
的潜在解决方法)。data.table
支持滚动联接(谢谢@dholstius)和重叠联接data.table
通过使用二进制搜索同时使用相同的基本R语法的自动索引,在内部优化了dt[col==value]
或dt[col%in%value]
形式的表达式,以提高速度。有关更多详细信息和一个小基准,请参阅此处。dplyr
提供了函数的标准评估版本(例如regroup
和summarize_each_
),这些函数可以简化dplyr
的编程使用(注意,data.table
的编程使用肯定是可能的,只是需要一些仔细的思考、替换/引用等,至少据我所知)data.table
会变得快得多。data.table
的扩展性优于dplyr
(在包和R的最新版本中都进行了更新)。此外,在尝试获取唯一值时,基准测试的data.table
速度快6x数据表
快75%,而在较小版本上dplyr
快40%(另一个来自注释的SO问题,谢谢danas)。data.table
的主要作者,他对data.table
、dplyr
和pythonpandas
进行了基准分组操作,最多可处理20亿行(~100GB内存)。数据.table
~8倍快这是我在问题部分中展示的第一个示例。
dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L,
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane",
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob",
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L,
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L,
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager",
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager",
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L,
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id",
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA,
-16L))
我们至少需要涵盖这些方面来提供全面的答案/比较(没有特定的重要性顺序):速度
、内存使用
、语法
和特性
。
我的目的是从Data.Table的角度尽可能清楚地介绍其中的每一个。
注意:除非另外明确提到,通过引用dplyr,我们指的是dplyr的data.frame接口,其内部是使用rcpp的C++。
data.table语法在其形式上是一致的-dt[i,j,by]
。要使i
、j
和by
保持在一起,就必须通过设计。通过将相关的操作保持在一起,它允许轻松地优化操作以提高速度,更重要的是提高内存使用,并且还提供了一些强大的特性,同时保持语法的一致性。
随着要分组的组和/或行的数量的增加,Table的速度比dplyr快,包括Matt对1亿到1000万组和不同分组列的1000万到20亿行(内存为100GB)进行分组的基准测试,这也比较了pandas
。另请参阅更新的基准测试,其中还包括spark
和pydataTable
。
关于基准,最好也包括以下几个方面:
> 涉及行子集的
分组操作--即dt[x>val,sum(y),by=z]
类型操作。
对其他操作(如更新和联接)进行基准测试。
除了运行时之外,还要对每个操作的内存占用进行基准测试。
当前data.table接口允许通过引用修改/更新列(注意,我们不需要将结果重新赋值回变量)。
# sub-assign by reference, updates 'y' in-place
DT[x >= 1L, y := NA]
但dplyr永远不会通过引用进行更新。dplyr等效值为(注意,需要重新分配结果):
# copies the entire 'y' column
ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))
这方面的一个问题是参考透明度。通过引用更新Data.Table对象,特别是在函数中,可能并不总是可取的。但这是一个非常有用的特性:有关有趣的案例,请参阅这篇文章和这篇文章。我们想保留它。
foo <- function(DT) {
DT = shallow(DT) ## shallow copy DT
DT[, newcol := 1L] ## does not affect the original DT
DT[x > 2L, newcol := 2L] ## no need to copy (internally), as this column exists only in shallow copied DT
DT[x > 2L, x := 3L] ## have to copy (like base R / dplyr does always); otherwise original DT will
## also get modified.
}
bar <- function(DT) {
DT[, newcol := 1L] ## old behaviour, original DT gets updated by reference
DT[x > 2L, x := 3L] ## old behaviour, update column x in original DT.
}
而且,一旦导出shallow()
,DPLYR的data.table接口应该避免几乎所有的副本。所以那些喜欢DPLYR语法的人可以将它与data.tables一起使用。
但是它仍然缺乏Data.Table提供的许多特性,包括(sub)-引用赋值。
加入时聚合:
假设您有两个Data.Table,如下所示:
DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y"))
# x y z
# 1: 1 a 1
# 2: 1 a 2
# 3: 1 b 3
# 4: 1 b 4
# 5: 2 a 5
# 6: 2 a 6
# 7: 2 b 7
# 8: 2 b 8
DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y"))
# x y mul
# 1: 1 a 4
# 2: 2 b 3
并且在按列x,y
连接时,您希望为DT2
中的每一行获取sum(z)*mul
。我们可以:
dt1[,.(z=sum(z)),keyby=.(x,y)][DT2][,z:=z*mul][]
DF1%>%group_by(x,y)%>%summise(z=sum(z))%>%right_join(DF2)%>%mutate(z=z*mul)
一次性完成所有操作(使用by=.eachi
特性):
DT1[DT2,列表(z=SUM(z)*mul),by=.EACHI]
优势在哪里?
>
我们不必为中间结果分配内存。
在dplyr
中,您必须连接并聚合或先聚合再连接,这两种方法在内存方面都没有那么高效(这反过来又转化为速度)。
更新和连接:
请考虑下面显示的数据表代码:
DT1[DT2, col := i.mul]
查看这篇文章以了解真实的使用场景。
总而言之,重要的是要认识到每一个优化都很重要。就像格蕾丝·霍珀说的,小心你的毫微秒!
现在让我们看看语法。哈德利在这里评论道:
DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
>
基本聚合/更新操作。
# case (a)
DT[, sum(y), by = z] ## data.table syntax
DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax
DT[, y := cumsum(y), by = z]
ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y))
# case (b)
DT[x > 2, sum(y), by = z]
DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y))
DT[x > 2, y := cumsum(y), by = z]
ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x > 2), cumsum(y)))
# case (c)
DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by = z]
DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L] - y[2L] else y[2L])
DT[, if(any(x > 5L)) y[1L] - y[2L], by = z]
DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L] - y[2L])
>
data.table语法紧凑,DPLYR的语法相当冗长。在情况(a)中,事物或多或少是等价的。
在情况(b)中,我们不得不在总结时使用dplyr中的filter()
。但在更新时,我们必须移动mutate()
中的逻辑。然而,在Data.Table中,我们用相同的逻辑来表示这两个操作--对x>2
的行进行操作,但在第一种情况下,得到sum(y)
,而在第二种情况下,用其累计和更新y
的行。
类似地,在情况(c)中,当我们具有if-else
条件时,我们能够在data.table和dplyr中“按原样”表示逻辑。但是,如果我们只想返回if
条件满足的那些行,而跳过否则,则不能直接使用summarise()
(AFAICT)。我们必须先filter()
,然后进行总结,因为summarise()
总是需要一个值。
虽然它返回相同的结果,但在这里使用filter()
会使实际操作不那么明显。
在第一种情况下也可以使用filter()
(在我看来并不明显),但我的观点是我们不应该非得这样做。
多列聚合/更新
# case (a)
DT[, lapply(.SD, sum), by = z] ## data.table syntax
DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax
DT[, (cols) := lapply(.SD, sum), by = z]
ans <- DF %>% group_by(z) %>% mutate_each(funs(sum))
# case (b)
DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by = z]
DF %>% group_by(z) %>% summarise_each(funs(sum, mean))
# case (c)
DT[, c(.N, lapply(.SD, sum)), by = z]
DF %>% group_by(z) %>% summarise_each(funs(n(), mean))
>
在情况(a)中,代码或多或少是等价的。data.table使用熟悉的基函数lapply()
,而dplyr
引入了*_each()
以及一组函数到funs()
。
Data.Table的:=
要求提供列名,而dplyr会自动生成列名。
在情况(b)中,DPLYR的语法相对简单。改进多个函数的聚合/更新列在Data.Table的列表中。
但是在情况(c)中,dplyr将返回n()
的次数与列的次数一样多,而不是只返回一次。在data.table中,我们需要做的只是在j
中返回一个列表。列表的每个元素都将成为结果中的一列。因此,我们可以再次使用熟悉的基函数c()
将.n
级联到一个list
,它返回一个list
。
注意:在data.table中,我们需要做的只是在j
中返回一个列表。列表中的每个元素都将成为Result中的一列。您可以使用c()
、as.list()
、lapply()
、list()
等。基本函数来完成这一点,而不需要学习任何新的函数。
您至少需要学习特殊变量.n
和.sd
。dplyr中的等价物是n()
和.
联接
dplyr为每种类型的联接提供了单独的函数,其中as data.table允许使用相同的语法dt[i,j,by]
(并且带有原因)进行联接。它还提供了一个等效的merge.data.table()
函数作为替代。
setkey(DT1, x, y)
# 1. normal join
DT1[DT2] ## data.table syntax
left_join(DT2, DT1) ## dplyr syntax
# 2. select columns while join
DT1[DT2, .(z, i.mul)]
left_join(select(DT2, x, y, mul), select(DT1, x, y, z))
# 3. aggregate while join
DT1[DT2, .(sum(z) * i.mul), by = .EACHI]
DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>%
inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul)
# 4. update while join
DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI]
??
# 5. rolling join
DT1[DT2, roll = -Inf]
??
# 6. other arguments to control output
DT1[DT2, mult = "first"]
??
data.tables可以在连接(2)时选择列,在dplyr中,您需要在两个data.frames上首先使用select()
才能连接,如上所示。否则,您将使用不必要的列对联接进行物化,以便稍后删除它们,这是低效的。
Data.Tables可以使用by=.eachi
特性(3)在连接时进行聚合,也可以在连接时进行更新(4)。为什么要将整个连接结果物化为只添加/更新几列?
data.table能够滚动连接(5)--向前滚动、LOCF、向后滚动、NOCB、最近。
Data.Table具有allow.cartesian=true
参数以防止意外的无效联接。
同样,语法与dt[i,j,by]
一致,带有允许进一步控制输出的附加参数。
do()
...
DT[, list(x[1], y[1]), by = z] ## data.table syntax
DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax
DT[, list(x[1:2], y[1]), by = z]
DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1]))
DT[, quantile(x, 0.25), by = z]
DF %>% group_by(z) %>% summarise(quantile(x, 0.25))
DT[, quantile(x, c(0.25, 0.75)), by = z]
DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75))))
DT[, as.list(summary(x)), by = z]
DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))
>
.sd
的等价物是.
在data.table中,您可以在j
中抛出几乎任何东西--唯一要记住的是它返回一个列表,以便列表的每个元素都被转换为一个列。
在dplyr中,不能这样做。必须求助于do()
,这取决于您对函数是否总是返回一个值的确定程度。而且相当慢。
看看这道SO题和这道题。我想知道是否可以用DPLYR的语法将答案表达得简单明了...
总而言之,我特别强调了DPLYR语法效率低、有限或操作不简单的几个实例。这特别是因为data.table在“更难读/学习”语法(如上面粘贴/链接的语法)方面受到了很大的反对。大多数涉及dplyr的帖子都讨论了最简单的操作。这太好了。但是认识到它的语法和特性限制也是很重要的,我还没有看到关于它的帖子。
data.table也有它的怪癖(我已经指出了我们正在尝试解决的一些怪癖)。我们还尝试改进data.table的联接,正如我在这里强调的那样。
但是还应该考虑dplyr与Data.Table相比所缺少的特性的数量。
我已经在这里和这篇文章中指出了大部分的特点。此外:
>
Fread-快速文件阅读器已经提供了很长时间。
上面提到了Data.Table联接的许多优点(速度/内存效率和语法)。
非等联接:允许使用其他运算符<=,<,>,>=
以及Data.Table联接的所有其他优点进行联接。
最近在Data.Table中实现了重叠的范围连接。请查看本篇文章,了解基准测试的概述。
data.table
提供了集合操作(由Jan Gorecki编写)更快的等价物-fsetdiff
、fintersect
、funion
和fsetequal
以及附加的all
参数(如SQL中的)。
data.table可以清晰地加载,没有屏蔽警告,并且在传递到任何R包时,这里描述了[.data.frame
兼容性的机制。dplyr更改了基函数filter
、lag
和[
,这些函数可能会导致问题,例如这里和这里。
最后:
>
在数据库上-没有理由data.table不能提供类似的接口,但这不是现在的优先事项。如果用户非常喜欢这个功能,它可能会被撞上。不确定。
关于并行性--一切都是困难的,直到有人去做它。当然,这需要付出努力(线程安全)。
我用Java重写了我的第一个程序,现在它看起来像这样: 实际上一切都很顺利,除了: > 当我在输入a、B和C的值时输入一个非数字类型时,会显示一个双重警告,然后它就会正常工作。(仅1个警告) 当我回答Y或N时,程序终止,但如果我输入任何其他内容,如“g”、“2”或“w”等,程序会再次询问我(第一次也是两次),然后当我输入Y或N,它工作得很好。 我真的不知道这是怎么回事,因为在编译过程中没有警告或错
这是take II,前几周我发了帖子,我的问题被搁置,我调整了我的文本,但无法得到评论,系统关闭了原来的帖子。 服务器端:只读-服务器打开管道,然后定期检查是否有内容(即不在流末尾)并读取信息。此检查必须以轮询为基础,因为只有在轮询期间,才有有效的上下文来传递数据。。 客户端:仅写-打开管道、写入管道、关闭(Client.exe多次调用,生命周期短,下面的代码是测试代码),例如,其他一些脚本将“使
我要离开一个网站(https://realpython.com/how-to-make-a-discord-bot-python/#how-to-make-a-discord-bot-in-python)和我下面的步骤,但我一直得到一个错误msg Traceback(最近一次调用最后一次): File"C:\用户\Bryce.Persello346\Desktop\bot.py",第15行,cli
问题内容: 一般而言,制作所有字段是一个好主意,但有时我发现自己在构造函数中做了所有事情。最近,我结束了一个类实际上做的 一切都 在构造函数中,包括读取属性文件并访问数据库。 一方面,这就是该类的用途,它封装了读取的数据,我喜欢创建完全初始化的对象。构造函数完全不复杂,因为它委派了大部分工作,因此看起来不错。 另一方面,感觉有点奇怪。此外,在大约17:58的演讲中,有充分的理由不对构造函数进行过多
在讲述有关list的时候,提到做游戏的事情,后来这个事情一直没有接续。不是忘记了,是在想在哪个阶段做最合适。经过一段时间学习,看官已经不是纯粹小白了,已经属于python初级者了。现在就是开始做那个游戏的时候了。 游戏内容:猜数字游戏 太简单了吧。是的,游戏难度不大,不过这个游戏中蕴含的东西可是值得玩味的。 游戏过程描述 程序运行起来,随机在某个范围内选择一个整数。 提示用户输入数字,也就是猜程序