匹配行和列名称中包含的数字的矩阵计算

Adr*_*ian 12 r matrix

我有一个矩阵,其中包含几个国家的行业经济信息。我想做一系列取决于矩阵中元素的位置和相应的列/行名称的计算。

行/列名称包含三个字母的国家/地区代码,后跟行业编号。行名称和列名称相同。

计算如下:

  1. 对于连续的每个元素,都会有一个匹配的国家和行业
  2. 保持产业组合不变,将纯同一国家的产业与比赛内其他国家的相同产业相乘。

我将举一个简单的例子,尽管我的数据比这个例子大得多。

假设我有矩阵a

set.seed(10)

a <- matrix(sample(36) , nrow = 6)
colnames(a) <- rownames(a) <- paste( rep(c("aaa" , "bbb" , "ccc") , each = 2), rep(c(1:2) , times = 3))
Run Code Online (Sandbox Code Playgroud)

给予:

      aaa 1 aaa 2 bbb 1 bbb 2 ccc 1 ccc 2
aaa 1     9    19    26     6     5    21
aaa 2    10    24     2    30    36    27
bbb 1    12    15    13    11    20    16
bbb 2     8    28    33    18    34    17
ccc 1    22    35    14    23    29     4
ccc 2     7    31    25    32     1     3
Run Code Online (Sandbox Code Playgroud)

在这种情况下,有三个国家“aaa”、“bbb”和“ccc”,只有两个产业,1和2。我想计算在自己国家生产的产业的价值,并相互相乘。

例如,在a[1, 4]此将“aaa 1”与“bbb 2”匹配。我想将每个国家的行业组合 1 - 2 相乘,即“aaa 1” - “aaa 2”与“bbb 1” - “bbb 2”(19 * 11 = 209)。沿对角线块的值将被平方(同一国家/地区的两倍)。

最终矩阵如下所示:

      aaa 1 aaa 2 bbb 1 bbb 2 ccc 1 ccc 2
aaa 1    81   361   117   209   261    76
aaa 2   100   576   330   432    10    72
bbb 1   117   209   169   121   377    44
bbb 2   330   432  1089   324    33    54
ccc 1   261    76   377    44   841    16
ccc 2    10    72    33    54     1     9
Run Code Online (Sandbox Code Playgroud)

我使用以下低效的代码手动计算:

b <- kronecker(diag(1, 3), matrix(1, 2, 2))

b <- (a * b)^2
c <- b

c[1,3] <- 9 * 13
c[1,4] <- 19 * 11
c[1,5] <- 9 * 29
c[1,6] <- 19 * 4

c[2,3] <- 10 * 33
c[2,4] <- 24 * 18
c[2,5] <- 10 * 1
c[2,6] <- 24 * 3

.
.
.

c[6,1] <- 1 * 10
c[6,2] <- 3 * 24
c[6,3] <- 1 * 33
c[6,4] <- 3 * 18
Run Code Online (Sandbox Code Playgroud)

是否有灵活的代码来计算此数据,无论数据中的国家和行业数量如何?我很感激任何帮助。

编辑:我正在使用的矩阵很大(尺寸超过 2000 * 2000)。我需要一个可以在不冻结 R 的情况下处理此类数据的代码。不幸的是, outer 可以解决此处针对较小的较小矩阵提出的问题,但在处理较大的矩阵时会陷入困境。

Dar*_*sai 6

假设每个国家都有相同数量的行业,并且dimnames(a)排序良好,如 A1、A2...Am、B1、B2、...Bm。这种方法利用了向量化计算的优势,避免了用暗名索引矩阵,从而提高了效率并显着节省了内存。

  • m: 行业数量
m <- 2
n <- nrow(a)
block <- matrix(a[kronecker(diag(n/m), matrix(1, m, m)) == 1], m)
block[rep(1:m, len = n), ] * c(block[, c(t(matrix(1:n, m)))])

#      [,1] [,2] [,3] [,4] [,5] [,6]
# [1,]   81  361  117  209  261   76
# [2,]  100  576  330  432   10   72
# [3,]  117  209  169  121  377   44
# [4,]  330  432 1089  324   33   54
# [5,]  261   76  377   44  841   16
# [6,]   10   72   33   54    1    9
Run Code Online (Sandbox Code Playgroud)


Mik*_*gan 6

a如果你的实际矩阵小于10000,这样的东西应该表现得足够好。你可以进一步优化,特别是如果你知道它们是n排序的(例如,参见@DarrenTsai 的答案),但这并不是明显必要的nndimnames(a)

n <- nrow(a)
nms <- rownames(a)

u <- unlist(strsplit(nms, " "))
country <- u[c(TRUE, FALSE)]
industry <- u[c(FALSE, TRUE)]

i1 <- seq_len(n)
j2 <- rep(i1, each = n)
j1 <- match(paste(country, industry[j2]), nms)
i2 <- match(paste(country[j2], industry), nms)

matrix(a[cbind(i1, j1)] * a[cbind(i2, j2)], n, n)
Run Code Online (Sandbox Code Playgroud)
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]   81  361  117  209  261   76
[2,]  100  576  330  432   10   72
[3,]  117  209  169  121  377   44
[4,]  330  432 1089  324   33   54
[5,]  261   76  377   44  841   16
[6,]   10   72   33   54    1    9
Run Code Online (Sandbox Code Playgroud)

当然,如果dimnames(a)未排序,但您想获得排序的好处,那么您可以排列 的行和列a,对生成的块矩阵进行操作,并对答案的行和列进行逆排列。

n <- nrow(a)
nms <- rownames(a)

## Number of industries, countries:
ni <- sum(startsWith(nms, substr(nms[1L], 1L, 3L)))
nc <- n %/% ni

p <- order(nms)
a.pp <- a[p, p] # permute

## Essentially @DarrenTsai's answer {uglier but highly optimized}:
k <- sequence(rep.int(ni, n), rep(seq.int(1L, n, ni), each = ni) + seq.int(0L, n * n - 1L, n))
b <- matrix(a.pp[k], ni, n)
r.pp <- b[rep.int(seq_len(ni), nc), ] * as.vector(b[, sequence(rep.int(nc, ni), seq_len(ni), ni)])

q <- p
q[q] <- seq_along(q)
r <- r.pp[q, q] # inverse permute
Run Code Online (Sandbox Code Playgroud)

实际上,这里有一些简洁的矩阵代数。Block [i,j]of是blocks和ofr.pp的元素乘积。因此可以看作 的一种块叉积,因为是 的对角块的水平串联。[i,i][j,j]a.ppr.ppbba.pp


GKi*_*GKi 5

另一种方法是将原始矩阵子集为所需的子矩阵并将它们相乘(GKi1)。

x <- do.call(rbind, strsplit(rownames(a), " "))
x <- apply(outer(unique(x[,1]), unique(x[,2]), paste), 1,
           \(i) a[i, i], simplify = FALSE)
x <- do.call(rbind, lapply(x, \(y) do.call(cbind, lapply(x, `*`, y))))
rownames(x) <- colnames(x)
x
#      aaa 1 aaa 2 bbb 1 bbb 2 ccc 1 ccc 2
#aaa 1    81   361   117   209   261    76
#aaa 2   100   576   330   432    10    72
#bbb 1   117   209   169   121   377    44
#bbb 2   330   432  1089   324    33    54
#ccc 1   261    76   377    44   841    16
#ccc 2    10    72    33    54     1     9

x[rownames(a), colnames(a)] #In case the order of the original matrix is needed
Run Code Online (Sandbox Code Playgroud)

一种变体,以不同的方式提取原始矩阵的相关信息 - 类似于@Mikael Jagan(谢谢!)的评论,可能看起来像(GKi1B)。

n <- nrow(a)
x <- do.call(rbind, strsplit(rownames(a), " ", TRUE))
x1 <- unique(x[,1])
x2 <- unique(x[,2])
n1 <- length(x1)
n2 <- length(x2)
A <- a[rep(match(t(outer(unique(x[,1]), unique(x[,2]), paste)), rownames(a)), each=n2) +
       sequence(rep.int(n2, n), sequence(rep.int(n2, n1), seq.int(0L, by=n*n2, length.out=n1), n-1L))]
do.call(rbind, lapply(seq.int(0, by=n2*n2, length.out=n1),
                      \(i) matrix(A * A[seq_len(n2*n2)+i], n2)) )
#     [,1] [,2] [,3] [,4] [,5] [,6]
#[1,]   81  361  117  209  261   76
#[2,]  100  576  330  432   10   72
#[3,]  117  209  169  121  377   44
#[4,]  330  432 1089  324   33   54
#[5,]  261   76  377   44  841   16
#[6,]   10   72   33   54    1    9
Run Code Online (Sandbox Code Playgroud)

另一种使用outer但也可能适用于更大矩阵的方法可能是(GKi2)。

n <- rownames(a)
i <- do.call(rbind, strsplit(n, " "))
j <- outer(i[,1], i[,2], paste)
array(a[cbind(n, c(j))] * a[cbind(c(t(j)), n[col(a)])], dim(a))
#     [,1] [,2] [,3] [,4] [,5] [,6]
#[1,]   81  361  117  209  261   76
#[2,]  100  576  330  432   10   72
#[3,]  117  209  169  121  377   44
#[4,]  330  432 1089  324   33   54
#[5,]  261   76  377   44  841   16
#[6,]   10   72   33   54    1    9
Run Code Online (Sandbox Code Playgroud)

基准

set.seed(10)
a <- matrix(sample(1:100, 2002*2002, TRUE) , nrow = 2002)
colnames(a) <- rownames(a) <- paste( rep(strrep(letters, 3) , each = 77), 1:77)

bench::mark(check=FALSE,
ThomasIsCoding = {p <- expand.grid(rep(list(row.names(a)), 2))
p1 <- as.matrix(transform(p, Var2 = paste(sub("\\d+", "", Var1), sub("\\D+", "", Var2), sep = "")))
p2 <- as.matrix(transform(p, Var1 = paste(sub("\\d+", "", Var2), sub("\\D+", "", Var1), sep = "")))
`dim<-`(a[p1] * a[p2], dim(a))},
"Mikael Jagan" = {n <- nrow(a)
nms <- rownames(a)
u <- unlist(strsplit(nms, " "))
country <- u[c(TRUE, FALSE)]
industry <- u[c(FALSE, TRUE)]
i1 <- seq_len(n)
j2 <- rep(i1, each = n)
j1 <- match(paste(country, industry[j2]), nms)
i2 <- match(paste(country[j2], industry), nms)
matrix(a[cbind(i1, j1)] * a[cbind(i2, j2)], n, n)},
GKi2 = {n <- rownames(a)
i <- do.call(rbind, strsplit(n, " "))
j <- outer(i[,1], i[,2], paste)
array(a[cbind(n, c(j))] * a[cbind(c(t(j)), n[col(a)])], dim(a))},
"Mikael & Darren" = {n <- nrow(a)
nms <- rownames(a)
ni <- sum(startsWith(nms, substr(nms[1L], 1L, 3L)))
nc <- n %/% ni
p <- order(nms)
a.pp <- a[p, p] # permute
b <- matrix(a.pp[as.logical(kronecker(diag(nc), matrix(1, ni, ni)))], ni, n)
r.pp <- b[rep.int(seq_len(ni), nc), ] * as.vector(b[, sequence(rep.int(nc, ni), seq_len(ni), ni)])
q <- p
q[q] <- seq_along(q)
r.pp[q, q]},
GKi1 = {x <- do.call(rbind, strsplit(rownames(a), " "))
x <- apply(outer(sort(unique(x[,1])), sort(unique(x[,2])), paste), 1,
           \(i) a[i, i], simplify = FALSE)
x <- do.call(rbind, lapply(x, \(y) do.call(cbind, lapply(x, `*`, y))))
rownames(x) <- colnames(x)
x},
"Mikael & DarrenB" = {n <- nrow(a)
nms <- rownames(a)
ni <- sum(startsWith(nms, substr(nms[1L], 1L, 3L)))
nc <- n %/% ni
p <- order(nms)
a.pp <- a[p, p] # permute
b <- {k <- sequence(rep.int(ni, n), rep(seq.int(1L, n, ni), each = ni) + seq(0L, n * n - 1L, n)); matrix(a.pp[k], ni, n)}
r.pp <- b[rep.int(seq_len(ni), nc), ] * as.vector(b[, sequence(rep.int(nc, ni), seq_len(ni), ni)])
q <- p
q[q] <- seq_along(q)
r.pp[q, q]},
GKi1B = {n <- nrow(a)
x <- do.call(rbind, strsplit(rownames(a), " ", TRUE))
x1 <- unique(x[,1])
x2 <- unique(x[,2])
n1 <- length(x1)
n2 <- length(x2)
A <- a[rep(match(t(outer(unique(x[,1]), unique(x[,2]), paste)), rownames(a)), each=n2) +
       sequence(rep.int(n2, n), sequence(rep.int(n2, n1), seq.int(0L, by=n*n2, length.out=n1), n-1L))]
do.call(rbind, lapply(seq.int(0, by=n2*n2, length.out=n1),
                      \(i) matrix(A * A[seq_len(n2*n2)+i], n2))) }
)
Run Code Online (Sandbox Code Playgroud)

结果

  expression            min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
  <bch:expr>       <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>
1 ThomasIsCoding      1.24s    1.24s     0.810   872.1MB     4.05     1     5
2 Mikael Jagan     743.23ms 743.23ms     1.35    367.1MB     1.35     1     1
3 GKi2             812.95ms 812.95ms     1.23    734.1MB     4.92     1     4
4 Mikael & Darren   62.56ms  64.66ms    15.5     140.7MB     9.69     8     5
5 GKi1              28.72ms  29.57ms    33.3      48.8MB     7.83    17     4
6 Mikael & DarrenB  28.21ms  32.37ms    30.9      48.9MB     5.79    16     3
7 GKi1B              26.7ms   28.3ms    35.0      49.8MB     9.71    18     5
Run Code Online (Sandbox Code Playgroud)

在本例中,Mikael & DarrenB、GKi1 和 GKiB 速度最快且使用的内存量最少。