检查一行中的所有值是否相同或缺失的最有效方法
The most efficient way to check if all values in a row are the same or are missing
我最初在 data.table
论坛上问过这个问题,并收到了一些很好的答案,但我希望找到一个不到一秒的解决方案(实际数据暗淡 3M x 303)
我在 R 社区的 post 和 julia 的 MWE(使用 DataFrames.jl 如果我使用了错误的包请告诉我)
julia> using DataFrames
julia> df = DataFrame(v1 = [1.0,2.1,3.0], v2 = [1,3,3], v3 = [missing,2.1,3.0])
3×3 DataFrame
Row │ v1 v2 v3
│ Float64 Int64 Float64?
─────┼───────────────────────────
1 │ 1.0 1 missing
2 │ 2.1 3 2.1
3 │ 3.0 3 3.0
julia> desired = [true,false,true]
3-element Vector{Bool}:
1
0
1
结构很重要。这是 R 中的矩阵方法,使用 matrixStats
包 (source),它提供了在 C 中实现的优化矩阵函数。
x = sample.int(3, size = 303*3e6, replace = T)
m = matrix(x, ncol = 303, byrow = T)
bench::mark(
var = matrixStats::rowVars(m, na.rm = T) == 0
)
在我的(当然不是高性能的)机器上,对于 300 万行矩阵,这大约需要 3.5 秒。
在 Julia 中使用矩阵进行此类操作,与 R 类似,也是很自然的:
julia> using BenchmarkTools
julia> function helper(x)
nonempty = false
local fv
for v in x
if !ismissing(v)
if nonempty
v == fv || return false
else
fv = v
nonempty = true
end
end
end
return true
end
helper (generic function with 1 method)
julia> mat = rand([1, 2, missing], 3_000_000, 303);
julia> @benchmark helper.(eachrow($mat))
BenchmarkTools.Trial: 34 samples with 1 evaluation.
Range (min … max): 139.440 ms … 154.628 ms ┊ GC (min … max): 5.74% … 5.15%
Time (median): 147.890 ms ┊ GC (median): 5.27%
Time (mean ± σ): 147.876 ms ± 3.114 ms ┊ GC (mean ± σ): 5.23% ± 0.95%
▃ ▃ ▃ ▃ ██ ▃
▇▁▁▁▁▁▁▁▁▁▇▁▁▁▁▁▁▁█▁▁▁▁█▇▇▇█▁▁█▇▁██▇█▁▇▁▇▇▁▇▇▁▇▁▇▇▇▁▁▁▁▇▁▁▁▁▇ ▁
139 ms Histogram: frequency by time 155 ms <
Memory estimate: 114.80 MiB, allocs estimate: 6.
该操作也可以在DataFrames.jl中完成,这里有一个例子:
julia> function helper2(x, i)
nonempty = false
local fv
for v in x
vv = v[i]
if !ismissing(vv)
if nonempty
vv == fv || return false
else
fv = vv
nonempty = true
end
end
end
return true
end
helper2 (generic function with 1 method)
julia> df = DataFrame(mat, :auto, copycols=false); # copycols to avoid copying as data is large
julia> @benchmark helper2.(Ref(identity.(eachcol($df))), 1:nrow($df))
BenchmarkTools.Trial: 46 samples with 1 evaluation.
Range (min … max): 105.265 ms … 123.345 ms ┊ GC (min … max): 0.00% … 0.00%
Time (median): 110.682 ms ┊ GC (median): 0.00%
Time (mean ± σ): 110.581 ms ± 2.692 ms ┊ GC (mean ± σ): 0.00% ± 0.00%
▄ ▂ ▄█▂
▄▁▁▄▄▁▁▆▄▁▁▁▆▆█▄█▆███▄▄▁█▄▁▁▁▁▁▁▁▁▁▁▁▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄ ▁
105 ms Histogram: frequency by time 123 ms <
Memory estimate: 385.28 KiB, allocs estimate: 15.
(如果代码中有任何不清楚的地方请告诉我,我可以解释)
编辑
如果你是一个独特的小联盟 eltypes
做:
helper2.(Ref(collect(Union{unique(typeof.(eachcol(df)))...}, eachcol(df))), 1:nrow(df))
如果 Union{unique(typeof.(eachcol(df)))...}
不是一个小集合,那么另一个解决方案会更好,如果这对您来说足够好,请发表评论。
你有大约 350MB 的 RAM 可用吗?如果是这样,你可以试试这个功能
rowequal <- function(x) {
undetermined <- function(x, can_del) {
if (length(can_del) < 1L)
return(x)
x[-can_del]
}
if (ncol(x) < 1L)
return(logical())
out <- logical(nrow(x))
if (ncol(x) < 2L)
return(!out)
x1 <- x[[1L]]
need_compare <- undetermined(seq_len(nrow(x)), which(x1 != x[[2L]]))
x1[nas] <- x[[2L]][nas <- which(is.na(x1))]
if (ncol(x) > 2L) {
for (j in 3:ncol(x)) {
need_compare <- undetermined(
need_compare, which(x1[need_compare] != x[[j]][need_compare])
)
x1[nas] <- x[[j]][nas <- which(is.na(x1))]
if (length(need_compare) < 1L)
return(out)
}
}
`[<-`(out, need_compare, TRUE)
}
以下是基准
> dim(d)
[1] 3000000 300
> bench::mark(f(d), rowequal(d), iterations = 10)
# A tibble: 2 x 13
expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <lis> <lis>
1 f(d) 2.97s 2.98s 0.335 34.4MB 0 10 0 29.8s <lgl ~ <Rpro~ <ben~ <tib~
2 rowequal(d) 88.52ms 93.34ms 10.7 352.2MB 0 10 0 932.5ms <lgl ~ <Rpro~ <ben~ <tib~
,其中 f
(我从 this post 得到的)和 d
是
f <- function(x) {
v1 = do.call(pmin, c(x, na.rm = TRUE))
v2 = do.call(pmax, c(x, na.rm = TRUE))
v1 == v2
}
mkDT <- function(rows, cols, type = as.integer) {
data.table::setDT(
replicate(cols, sample(type(c(1:10, NA)), rows, replace = TRUE), simplify = FALSE)
)
}
d <- mkDT(3e6, 300)
代码的 Rcpp 版本。如果您可以确保数据框中的所有列都是 numeric
类型,则它可以实现最佳性能(在内存使用和速度方面)。我想这是解决您问题的最有效方法(在 R 中)。
rowequalcpp <- function(x) {
if (ncol(x) < 1L)
return(logical())
out <- logical(nrow(x))
if (ncol(x) < 2L)
return(!out)
mark_equal(out, x)
out
}
Rcpp::sourceCpp(code = '
#include <Rcpp.h>
// [[Rcpp::export]]
void mark_equal(Rcpp::LogicalVector& res, const Rcpp::DataFrame& df) {
Rcpp::NumericVector x1 = df[0];
int n = df.nrows();
int need_compare[n];
for (int i = 0; i < n; ++i)
need_compare[i] = i;
for (int j = 1; j < df.length(); ++j) {
Rcpp::NumericVector dfj = df[j];
for (int i = 0; i < n; ) {
int itmp = need_compare[i];
if (Rcpp::NumericVector::is_na(x1[itmp]))
x1[itmp] = dfj[itmp];
if (!Rcpp::NumericVector::is_na(dfj[itmp]) && x1[itmp] != dfj[itmp]) {
need_compare[i] = need_compare[n - 1];
need_compare[n-- - 1] = itmp;
} else
++i;
}
if (n < 1)
return;
}
for (int i = 0; i < n; ++i)
res[need_compare[i]] = TRUE;
}
')
基准(列的类型对性能至关重要):
> d <- mkDT(3000000, 300, as.integer)
> bench::mark(rowequal(d), rowequalcpp(d), iterations = 10)
# A tibble: 2 x 13
expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc
<bch:expr> <bch:> <bch:t> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <lis> <lis>
1 rowequal(d) 100ms 147ms 7.07 398MB 3.03 7 3 991ms <lgl ~ <Rpro~ <ben~ <tib~
2 rowequalcpp(d) 101ms 102ms 9.35 309MB 2.34 8 2 855ms <lgl ~ <Rpro~ <ben~ <tib~
> d <- mkDT(3000000, 300, as.numeric)
> bench::mark(rowequal(d), rowequalcpp(d), iterations = 10)
# A tibble: 2 x 13
expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <lis>
1 rowequal(d) 103.7ms 110.8ms 8.05 349.3MB 0.895 9 1 1.12s <lgl [~ <Rprofm~ <ben~
2 rowequalcpp(d) 26.3ms 27.3ms 36.3 11.4MB 0 10 0 275.2ms <lgl [~ <Rprofm~ <ben~
# ... with 1 more variable: gc <list>
我建议你使用 python
,它有两个很棒的产品,pandas
最流行的数据框架库和 polars
世界上最快的数据框架库! (db-benchmark).
import time
t1=time.time();df.max(axis=1)==df.min(axis=1);t2=time.time()
关于这个问题的快速更新,
我设法使用以下函数将时间减少到大约 2 秒(少于 3 MiB 内存使用量!),尚未使用并行性:(魔法在于使用 @.
)
julia> f(x,y) = ismissing(x) || ismissing(y) ? true : x == y
julia> function rowequal(df, cols)
res=ones(Bool, nrow(df))
for i in 2:length(cols)
@. res &= ifelse(f(df[!, cols[i-1]], df[!,cols[i]]), true, false)
end
res
end
关于此的另一个更新,现在我可以使用 InMemoryDatasets
将时间减少到不到一秒(实际上不到 0.5 秒),这要归功于 Julia lang 话语中的这个 discussion。
julia> using InMemoryDatasets
julia> df = Dataset(df)
julia> f(x,y) = ismissing(x) || ismissing(y) ? true : x == y
julia> byrow(df, isless, [:v1, :v2, :v3], with = byrow(df, coalesce, :), lt = f)
更新
通过话语讨论的新更新,我几乎可以达到之前疯狂记录的一半 :D,即 0.27(Julia 社区真的很喜欢 SPEED)
julia> byrow(df, issorted, [:v1, :v2, :v3], lt = !f)
我最初在 data.table
论坛上问过这个问题,并收到了一些很好的答案,但我希望找到一个不到一秒的解决方案(实际数据暗淡 3M x 303)
我在 R 社区的 post 和 julia 的 MWE(使用 DataFrames.jl 如果我使用了错误的包请告诉我)
julia> using DataFrames
julia> df = DataFrame(v1 = [1.0,2.1,3.0], v2 = [1,3,3], v3 = [missing,2.1,3.0])
3×3 DataFrame
Row │ v1 v2 v3
│ Float64 Int64 Float64?
─────┼───────────────────────────
1 │ 1.0 1 missing
2 │ 2.1 3 2.1
3 │ 3.0 3 3.0
julia> desired = [true,false,true]
3-element Vector{Bool}:
1
0
1
结构很重要。这是 R 中的矩阵方法,使用 matrixStats
包 (source),它提供了在 C 中实现的优化矩阵函数。
x = sample.int(3, size = 303*3e6, replace = T)
m = matrix(x, ncol = 303, byrow = T)
bench::mark(
var = matrixStats::rowVars(m, na.rm = T) == 0
)
在我的(当然不是高性能的)机器上,对于 300 万行矩阵,这大约需要 3.5 秒。
在 Julia 中使用矩阵进行此类操作,与 R 类似,也是很自然的:
julia> using BenchmarkTools
julia> function helper(x)
nonempty = false
local fv
for v in x
if !ismissing(v)
if nonempty
v == fv || return false
else
fv = v
nonempty = true
end
end
end
return true
end
helper (generic function with 1 method)
julia> mat = rand([1, 2, missing], 3_000_000, 303);
julia> @benchmark helper.(eachrow($mat))
BenchmarkTools.Trial: 34 samples with 1 evaluation.
Range (min … max): 139.440 ms … 154.628 ms ┊ GC (min … max): 5.74% … 5.15%
Time (median): 147.890 ms ┊ GC (median): 5.27%
Time (mean ± σ): 147.876 ms ± 3.114 ms ┊ GC (mean ± σ): 5.23% ± 0.95%
▃ ▃ ▃ ▃ ██ ▃
▇▁▁▁▁▁▁▁▁▁▇▁▁▁▁▁▁▁█▁▁▁▁█▇▇▇█▁▁█▇▁██▇█▁▇▁▇▇▁▇▇▁▇▁▇▇▇▁▁▁▁▇▁▁▁▁▇ ▁
139 ms Histogram: frequency by time 155 ms <
Memory estimate: 114.80 MiB, allocs estimate: 6.
该操作也可以在DataFrames.jl中完成,这里有一个例子:
julia> function helper2(x, i)
nonempty = false
local fv
for v in x
vv = v[i]
if !ismissing(vv)
if nonempty
vv == fv || return false
else
fv = vv
nonempty = true
end
end
end
return true
end
helper2 (generic function with 1 method)
julia> df = DataFrame(mat, :auto, copycols=false); # copycols to avoid copying as data is large
julia> @benchmark helper2.(Ref(identity.(eachcol($df))), 1:nrow($df))
BenchmarkTools.Trial: 46 samples with 1 evaluation.
Range (min … max): 105.265 ms … 123.345 ms ┊ GC (min … max): 0.00% … 0.00%
Time (median): 110.682 ms ┊ GC (median): 0.00%
Time (mean ± σ): 110.581 ms ± 2.692 ms ┊ GC (mean ± σ): 0.00% ± 0.00%
▄ ▂ ▄█▂
▄▁▁▄▄▁▁▆▄▁▁▁▆▆█▄█▆███▄▄▁█▄▁▁▁▁▁▁▁▁▁▁▁▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄ ▁
105 ms Histogram: frequency by time 123 ms <
Memory estimate: 385.28 KiB, allocs estimate: 15.
(如果代码中有任何不清楚的地方请告诉我,我可以解释)
编辑
如果你是一个独特的小联盟 eltypes
做:
helper2.(Ref(collect(Union{unique(typeof.(eachcol(df)))...}, eachcol(df))), 1:nrow(df))
如果 Union{unique(typeof.(eachcol(df)))...}
不是一个小集合,那么另一个解决方案会更好,如果这对您来说足够好,请发表评论。
你有大约 350MB 的 RAM 可用吗?如果是这样,你可以试试这个功能
rowequal <- function(x) {
undetermined <- function(x, can_del) {
if (length(can_del) < 1L)
return(x)
x[-can_del]
}
if (ncol(x) < 1L)
return(logical())
out <- logical(nrow(x))
if (ncol(x) < 2L)
return(!out)
x1 <- x[[1L]]
need_compare <- undetermined(seq_len(nrow(x)), which(x1 != x[[2L]]))
x1[nas] <- x[[2L]][nas <- which(is.na(x1))]
if (ncol(x) > 2L) {
for (j in 3:ncol(x)) {
need_compare <- undetermined(
need_compare, which(x1[need_compare] != x[[j]][need_compare])
)
x1[nas] <- x[[j]][nas <- which(is.na(x1))]
if (length(need_compare) < 1L)
return(out)
}
}
`[<-`(out, need_compare, TRUE)
}
以下是基准
> dim(d)
[1] 3000000 300
> bench::mark(f(d), rowequal(d), iterations = 10)
# A tibble: 2 x 13
expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <lis> <lis>
1 f(d) 2.97s 2.98s 0.335 34.4MB 0 10 0 29.8s <lgl ~ <Rpro~ <ben~ <tib~
2 rowequal(d) 88.52ms 93.34ms 10.7 352.2MB 0 10 0 932.5ms <lgl ~ <Rpro~ <ben~ <tib~
,其中 f
(我从 this post 得到的)和 d
是
f <- function(x) {
v1 = do.call(pmin, c(x, na.rm = TRUE))
v2 = do.call(pmax, c(x, na.rm = TRUE))
v1 == v2
}
mkDT <- function(rows, cols, type = as.integer) {
data.table::setDT(
replicate(cols, sample(type(c(1:10, NA)), rows, replace = TRUE), simplify = FALSE)
)
}
d <- mkDT(3e6, 300)
代码的 Rcpp 版本。如果您可以确保数据框中的所有列都是 numeric
类型,则它可以实现最佳性能(在内存使用和速度方面)。我想这是解决您问题的最有效方法(在 R 中)。
rowequalcpp <- function(x) {
if (ncol(x) < 1L)
return(logical())
out <- logical(nrow(x))
if (ncol(x) < 2L)
return(!out)
mark_equal(out, x)
out
}
Rcpp::sourceCpp(code = '
#include <Rcpp.h>
// [[Rcpp::export]]
void mark_equal(Rcpp::LogicalVector& res, const Rcpp::DataFrame& df) {
Rcpp::NumericVector x1 = df[0];
int n = df.nrows();
int need_compare[n];
for (int i = 0; i < n; ++i)
need_compare[i] = i;
for (int j = 1; j < df.length(); ++j) {
Rcpp::NumericVector dfj = df[j];
for (int i = 0; i < n; ) {
int itmp = need_compare[i];
if (Rcpp::NumericVector::is_na(x1[itmp]))
x1[itmp] = dfj[itmp];
if (!Rcpp::NumericVector::is_na(dfj[itmp]) && x1[itmp] != dfj[itmp]) {
need_compare[i] = need_compare[n - 1];
need_compare[n-- - 1] = itmp;
} else
++i;
}
if (n < 1)
return;
}
for (int i = 0; i < n; ++i)
res[need_compare[i]] = TRUE;
}
')
基准(列的类型对性能至关重要):
> d <- mkDT(3000000, 300, as.integer)
> bench::mark(rowequal(d), rowequalcpp(d), iterations = 10)
# A tibble: 2 x 13
expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc
<bch:expr> <bch:> <bch:t> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <lis> <lis>
1 rowequal(d) 100ms 147ms 7.07 398MB 3.03 7 3 991ms <lgl ~ <Rpro~ <ben~ <tib~
2 rowequalcpp(d) 101ms 102ms 9.35 309MB 2.34 8 2 855ms <lgl ~ <Rpro~ <ben~ <tib~
> d <- mkDT(3000000, 300, as.numeric)
> bench::mark(rowequal(d), rowequalcpp(d), iterations = 10)
# A tibble: 2 x 13
expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <lis>
1 rowequal(d) 103.7ms 110.8ms 8.05 349.3MB 0.895 9 1 1.12s <lgl [~ <Rprofm~ <ben~
2 rowequalcpp(d) 26.3ms 27.3ms 36.3 11.4MB 0 10 0 275.2ms <lgl [~ <Rprofm~ <ben~
# ... with 1 more variable: gc <list>
我建议你使用 python
,它有两个很棒的产品,pandas
最流行的数据框架库和 polars
世界上最快的数据框架库! (db-benchmark).
import time
t1=time.time();df.max(axis=1)==df.min(axis=1);t2=time.time()
关于这个问题的快速更新,
我设法使用以下函数将时间减少到大约 2 秒(少于 3 MiB 内存使用量!),尚未使用并行性:(魔法在于使用 @.
)
julia> f(x,y) = ismissing(x) || ismissing(y) ? true : x == y
julia> function rowequal(df, cols)
res=ones(Bool, nrow(df))
for i in 2:length(cols)
@. res &= ifelse(f(df[!, cols[i-1]], df[!,cols[i]]), true, false)
end
res
end
关于此的另一个更新,现在我可以使用 InMemoryDatasets
将时间减少到不到一秒(实际上不到 0.5 秒),这要归功于 Julia lang 话语中的这个 discussion。
julia> using InMemoryDatasets
julia> df = Dataset(df)
julia> f(x,y) = ismissing(x) || ismissing(y) ? true : x == y
julia> byrow(df, isless, [:v1, :v2, :v3], with = byrow(df, coalesce, :), lt = f)
更新
通过话语讨论的新更新,我几乎可以达到之前疯狂记录的一半 :D,即 0.27(Julia 社区真的很喜欢 SPEED)
julia> byrow(df, issorted, [:v1, :v2, :v3], lt = !f)