Быстрая попарная простая линейная регрессия между переменными в кадре данных
я много раз видел попарную или общую парную простую линейную регрессию при переполнении стека. Вот набор данных игрушек для такого рода проблем.
set.seed(0)
X <- matrix(runif(100), 100, 5, dimnames = list(1:100, LETTERS[1:5]))
b <- c(1, 0.7, 1.3, 2.9, -2)
dat <- X * b[col(X)] + matrix(rnorm(100 * 5, 0, 0.1), 100, 5)
dat <- as.data.frame(dat)
pairs(dat)
поэтому в основном мы хотим вычислить 5 * 4 = 20 линий регрессии:
----- A ~ B A ~ C A ~ D A ~ E
B ~ A ----- B ~ C B ~ D B ~ E
C ~ A C ~ B ----- C ~ D C ~ E
D ~ A D ~ B D ~ C ----- D ~ E
E ~ A E ~ B E ~ C E ~ D -----
здесь плохого человека стратегия:
poor <- function (dat) {
n <- nrow(dat)
p <- ncol(dat)
## all formulae
LHS <- rep(colnames(dat), p)
RHS <- rep(colnames(dat), each = p)
## function to fit and summarize a single model
fitmodel <- function (LHS, RHS) {
if (RHS == LHS) {
z <- data.frame("LHS" = LHS, "RHS" = RHS,
"alpha" = 0,
"beta" = 1,
"beta.se" = 0,
"beta.tv" = Inf,
"beta.pv" = 0,
"sig" = 0,
"R2" = 1,
"F.fv" = Inf,
"F.pv" = 0,
stringsAsFactors = FALSE)
} else {
result <- summary(lm(reformulate(RHS, LHS), data = dat))
z <- data.frame("LHS" = LHS, "RHS" = RHS,
"alpha" = result$coefficients[1, 1],
"beta" = result$coefficients[2, 1],
"beta.se" = result$coefficients[2, 2],
"beta.tv" = result$coefficients[2, 3],
"beta.pv" = result$coefficients[2, 4],
"sig" = result$sigma,
"R2" = result$r.squared,
"F.fv" = result$fstatistic[[1]],
"F.pv" = pf(result$fstatistic[[1]], 1, n - 2, lower.tail = FALSE),
stringsAsFactors = FALSE)
}
z
}
## loop through all models
do.call("rbind.data.frame", c(Map(fitmodel, LHS, RHS),
list(make.row.names = FALSE,
stringsAsFactors = FALSE)))
}
логика ясна: получить все пары, построить формулу модели (reformulate
), соответствует регрессии (lm
), сделать резюме summary
, возвратить всю статистику и rbind
они должны быть фреймом данных.
хорошо, хорошо, но что, если есть p
переменные? Нам тогда нужно сделать p * (p - 1)
регрессий!
немедленное улучшение, о котором я мог бы подумать, это установка линейной модели с несколькими LHS. Например, первый столбец этой матрицы формул объединяется в
cbind(B, C, D, E) ~ A
это уменьшает количество регрессий от p * (p - 1)
to p
.
но мы можем определенно сделать даже лучше без использования lm
и summary
. Вот моя предыдущая попытка: есть ли быстрая оценка простой регрессии (линия регрессии с только перехватом и наклоном)?. Это быстро, потому что он использует ковариацию между переменными для оценки, например, решение нормальное уравнение. Но ... --16--> функция там довольно ограничена:
- ему нужно вычислить остаточные векторы для оценить остаточную стандартную ошибку, которая является узким местом производительности;
- он не поддерживает несколько LHS, поэтому его нужно назвать
p * (p - 1)
раз в настройках попарно регрессии).
можем ли мы обобщить его для быстрой попарной регрессии, написав функцию pairwise_simpleLM
?
общая парная простая линейная регрессия
более полезным вариантом вышеуказанной попарной регрессии является общая парная регрессия между набором Переменные LHS и набор переменных RHS.
Пример 1
установите парную регрессию между переменными LHS A
, B
, C
и переменные RHS D
, E
, то есть подходят 6 простых линий линейной регрессии:
A ~ D A ~ E
B ~ D B ~ E
C ~ D C ~ E
Пример 2
установите простую линейную регрессию с несколькими переменными LHS на определенную переменную RHS, скажем:cbind(A, B, C, D) ~ E
.
пример 3
установите простую линейную регрессию с определенной переменной LHS и набор переменных RHS по одному за раз, например:
A ~ B A ~ C A ~ D A ~ E
можем ли мы также иметь быструю функцию general_paired_simpleLM
для этого?
осторожностью
- все переменные должны быть числовыми; факторы не допускаются или попарная регрессия не имеет смысла.
- взвешенная регрессия не обсуждается, так как метод дисперсии-ковариации в таком случае это не оправдано.
1 ответов
некоторые статистические результаты / фон
(ссылка на фото: функция для вычисления R2 (r-квадрат) в R)
вычислительные детали
вычисления, связанные здесь, в основном являются вычислением матрицы дисперсии-ковариации. Как только мы его получим, результаты для всей попарной регрессии-это просто элементарная матричная арифметика.
ковариационную матрица может быть получена функцией R cov
, но функции ниже вычислить его вручную с помощью crossprod
. Преимущество в том, что он, очевидно, может извлечь выгоду из оптимизированной библиотеки BLAS, если у вас есть. Имейте в виду, что таким образом достигается значительное упрощение. Функция R cov
Аргуметы use
что позволяет регулировать NA
, а crossprod
нет. Я предполагаю, что ваш dat
не имеет отсутствующих значений вообще! Если у вас есть отсутствующие значения, удалите их себя na.omit(dat)
.
исходная as.matrix
это преобразование фрейма данных в матрицу может быть накладными расходами. В принципе, если я закодирую все на C / C++, я могу устранить это принуждение. И на самом деле, многие элементарные матричные арифметические матрицы могут быть объединены в одно петлевое гнездо. Тем не менее, я действительно беспокоюсь об этом в данный момент (так как у меня нет времени).
некоторые люди могут утверждать, что формат окончательного возвращения неудобен. Могут быть и другие формат:
- список фреймов данных, каждый из которых дает результат регрессии для конкретной переменной LHS;
- список фреймов данных, каждый из которых дает результат регрессии для определенной переменной RHS.
это действительно личное мнение. В любом случае, вы всегда можете сделать split.data.frame
по столбцу" LHS "или столбцу" RHS " самостоятельно на фрейме данных я возвращаю вас.
функция R pairwise_simpleLM
pairwise_simpleLM <- function (dat) {
## matrix and its dimension (n: numbeta.ser of data; p: numbeta.ser of variables)
dat <- as.matrix(dat)
n <- nrow(dat)
p <- ncol(dat)
## variable summary: mean, (unscaled) covariance and (unscaled) variance
m <- colMeans(dat)
V <- crossprod(dat) - tcrossprod(m * sqrt(n))
d <- diag(V)
## R-squared (explained variance) and its complement
R2 <- (V ^ 2) * tcrossprod(1 / d)
R2_complement <- 1 - R2
R2_complement[seq.int(from = 1, by = p + 1, length = p)] <- 0
## slope and intercept
beta <- V * rep(1 / d, each = p)
alpha <- m - beta * rep(m, each = p)
## residual sum of squares and standard error
RSS <- R2_complement * d
sig <- sqrt(RSS * (1 / (n - 2)))
## statistics for slope
beta.se <- sig * rep(1 / sqrt(d), each = p)
beta.tv <- beta / beta.se
beta.pv <- 2 * pt(abs(beta.tv), n - 2, lower.tail = FALSE)
## F-statistic and p-value
F.fv <- (n - 2) * R2 / R2_complement
F.pv <- pf(F.fv, 1, n - 2, lower.tail = FALSE)
## export
data.frame(LHS = rep(colnames(dat), times = p),
RHS = rep(colnames(dat), each = p),
alpha = c(alpha),
beta = c(beta),
beta.se = c(beta.se),
beta.tv = c(beta.tv),
beta.pv = c(beta.pv),
sig = c(sig),
R2 = c(R2),
F.fv = c(F.fv),
F.pv = c(F.pv),
stringsAsFactors = FALSE)
}
давайте сравним результат на наборе данных игрушек в вопросе.
oo <- poor(dat)
rr <- pairwise_simpleLM(dat)
all.equal(oo, rr)
#[1] TRUE
давайте посмотрим его выход:
rr[1:3, ]
# LHS RHS alpha beta beta.se beta.tv beta.pv sig
#1 A A 0.00000000 1.0000000 0.00000000 Inf 0.000000e+00 0.0000000
#2 B A 0.05550367 0.6206434 0.04456744 13.92594 5.796437e-25 0.1252402
#3 C A 0.05809455 1.2215173 0.04790027 25.50126 4.731618e-45 0.1346059
# R2 F.fv F.pv
#1 1.0000000 Inf 0.000000e+00
#2 0.6643051 193.9317 5.796437e-25
#3 0.8690390 650.3142 4.731618e-45
когда у нас одинаковые LHS и RHS, регрессия бессмысленна, следовательно, перехват равен 0, наклон равен 1 и т. д.
как насчет скорости? Все еще используя этот пример игрушки:
library(microbenchmark)
microbenchmark("poor_man's" = poor(dat), "fast" = pairwise_simpleLM(dat))
#Unit: milliseconds
# expr min lq mean median uq max
# poor_man's 127.270928 129.060515 137.813875 133.390722 139.029912 216.24995
# fast 2.732184 3.025217 3.381613 3.134832 3.313079 10.48108
разрыв будет все шире, поскольку у нас больше переменных. Например, с 10 переменными есть:
set.seed(0)
X <- matrix(runif(100), 100, 10, dimnames = list(1:100, LETTERS[1:10]))
b <- runif(10)
DAT <- X * b[col(X)] + matrix(rnorm(100 * 10, 0, 0.1), 100, 10)
DAT <- as.data.frame(DAT)
microbenchmark("poor_man's" = poor(DAT), "fast" = pairwise_simpleLM(DAT))
#Unit: milliseconds
# expr min lq mean median uq max
# poor_man's 548.949161 551.746631 573.009665 556.307448 564.28355 801.645501
# fast 3.365772 3.578448 3.721131 3.621229 3.77749 6.791786
функции R general_paired_simpleLM
general_paired_simpleLM <- function (dat_LHS, dat_RHS) {
## matrix and its dimension (n: numbeta.ser of data; p: numbeta.ser of variables)
dat_LHS <- as.matrix(dat_LHS)
dat_RHS <- as.matrix(dat_RHS)
if (nrow(dat_LHS) != nrow(dat_RHS)) stop("'dat_LHS' and 'dat_RHS' don't have same number of rows!")
n <- nrow(dat_LHS)
pl <- ncol(dat_LHS)
pr <- ncol(dat_RHS)
## variable summary: mean, (unscaled) covariance and (unscaled) variance
ml <- colMeans(dat_LHS)
mr <- colMeans(dat_RHS)
vl <- colSums(dat_LHS ^ 2) - ml * ml * n
vr <- colSums(dat_RHS ^ 2) - mr * mr * n
##V <- crossprod(dat - rep(m, each = n)) ## cov(u, v) = E[(u - E[u])(v - E[v])]
V <- crossprod(dat_LHS, dat_RHS) - tcrossprod(ml * sqrt(n), mr * sqrt(n)) ## cov(u, v) = E[uv] - E{u]E[v]
## R-squared (explained variance) and its complement
R2 <- (V ^ 2) * tcrossprod(1 / vl, 1 / vr)
R2_complement <- 1 - R2
## slope and intercept
beta <- V * rep(1 / vr, each = pl)
alpha <- ml - beta * rep(mr, each = pl)
## residual sum of squares and standard error
RSS <- R2_complement * vl
sig <- sqrt(RSS * (1 / (n - 2)))
## statistics for slope
beta.se <- sig * rep(1 / sqrt(vr), each = pl)
beta.tv <- beta / beta.se
beta.pv <- 2 * pt(abs(beta.tv), n - 2, lower.tail = FALSE)
## F-statistic and p-value
F.fv <- (n - 2) * R2 / R2_complement
F.pv <- pf(F.fv, 1, n - 2, lower.tail = FALSE)
## export
data.frame(LHS = rep(colnames(dat_LHS), times = pr),
RHS = rep(colnames(dat_RHS), each = pl),
alpha = c(alpha),
beta = c(beta),
beta.se = c(beta.se),
beta.tv = c(beta.tv),
beta.pv = c(beta.pv),
sig = c(sig),
R2 = c(R2),
F.fv = c(F.fv),
F.pv = c(F.pv),
stringsAsFactors = FALSE)
}
применить к Пример 1 в этом вопросе.
general_paired_simpleLM(dat[1:3], dat[4:5])
# LHS RHS alpha beta beta.se beta.tv beta.pv sig
#1 A D -0.009212582 0.3450939 0.01171768 29.45071 1.772671e-50 0.09044509
#2 B D 0.012474593 0.2389177 0.01420516 16.81908 1.201421e-30 0.10964516
#3 C D -0.005958236 0.4565443 0.01397619 32.66585 1.749650e-54 0.10787785
#4 A E 0.008650812 -0.4798639 0.01963404 -24.44040 1.738263e-43 0.10656866
#5 B E 0.012738403 -0.3437776 0.01949488 -17.63426 3.636655e-32 0.10581331
#6 C E 0.009068106 -0.6430553 0.02183128 -29.45569 1.746439e-50 0.11849472
# R2 F.fv F.pv
#1 0.8984818 867.3441 1.772671e-50
#2 0.7427021 282.8815 1.201421e-30
#3 0.9158840 1067.0579 1.749650e-54
#4 0.8590604 597.3333 1.738263e-43
#5 0.7603718 310.9670 3.636655e-32
#6 0.8985126 867.6375 1.746439e-50
применить к Пример 2 в этом вопросе.
general_paired_simpleLM(dat[1:4], dat[5])
# LHS RHS alpha beta beta.se beta.tv beta.pv sig
#1 A E 0.008650812 -0.4798639 0.01963404 -24.44040 1.738263e-43 0.1065687
#2 B E 0.012738403 -0.3437776 0.01949488 -17.63426 3.636655e-32 0.1058133
#3 C E 0.009068106 -0.6430553 0.02183128 -29.45569 1.746439e-50 0.1184947
#4 D E 0.066190196 -1.3767586 0.03597657 -38.26820 9.828853e-61 0.1952718
# R2 F.fv F.pv
#1 0.8590604 597.3333 1.738263e-43
#2 0.7603718 310.9670 3.636655e-32
#3 0.8985126 867.6375 1.746439e-50
#4 0.9372782 1464.4551 9.828853e-61
применить к Пример 3 в этом вопросе.
general_paired_simpleLM(dat[1], dat[2:5])
# LHS RHS alpha beta beta.se beta.tv beta.pv sig
#1 A B 0.112229318 1.0703491 0.07686011 13.92594 5.796437e-25 0.16446951
#2 A C 0.025628210 0.7114422 0.02789832 25.50126 4.731618e-45 0.10272687
#3 A D -0.009212582 0.3450939 0.01171768 29.45071 1.772671e-50 0.09044509
#4 A E 0.008650812 -0.4798639 0.01963404 -24.44040 1.738263e-43 0.10656866
# R2 F.fv F.pv
#1 0.6643051 193.9317 5.796437e-25
#2 0.8690390 650.3142 4.731618e-45
#3 0.8984818 867.3441 1.772671e-50
#4 0.8590604 597.3333 1.738263e-43
мы можем даже просто сделать простую линейную регрессию между двумя переменными:
general_paired_simpleLM(dat[1], dat[2])
# LHS RHS alpha beta beta.se beta.tv beta.pv sig
#1 A B 0.1122293 1.070349 0.07686011 13.92594 5.796437e-25 0.1644695
# R2 F.fv F.pv
#1 0.6643051 193.9317 5.796437e-25
это означает, что