Вот мой игрушечный фреймворк.
df <- tibble::tribble(
~var1, ~var2, ~var3, ~var4, ~var5, ~var6, ~var7,
"A", "C", 1L, 5L, "AA", "AB", 1L,
"A", "C", 2L, 5L, "BB", "AC", 2L,
"A", "D", 1L, 7L, "AA", "BC", 2L,
"A", "D", 2L, 3L, "BB", "CC", 1L,
"B", "C", 1L, 8L, "AA", "AB", 1L,
"B", "C", 2L, 6L, "BB", "AC", 2L,
"B", "D", 1L, 9L, "AA", "BC", 2L,
"B", "D", 2L, 6L, "BB", "CC", 1L)
Как я могу получить комбинацию минимального количества переменных, которые однозначно идентифицируют наблюдения в фрейме данных, то есть какие переменные вместе могут составлять основной ключ?
Я подошел к этой проблеме, чтобы найти комбинацию переменных, различные значения которых равны количеству наблюдений в фрейме данных. Итак, те комбинации переменных, которые в данном случае дадут мне 8 наблюдений. Я случайно попробовал это и нашел несколько:
df %>% distinct(var1, var2, var3)
df %>% distinct(var1, var2, var5)
df %>% distinct(var1, var3, var7)
Таким образом, vars123, vars125, vars137 заслуживают здесь первичного ключа. Как я могу найти эти комбинации переменных программно с помощью R. Кроме того, больше предпочтений следует отдавать символьным, факторным, датам и (возможно) целочисленным переменным, если это возможно, поскольку двойные значения не должны составлять первичный ключ.
Результатом может быть список или фрейм данных с указанием комбинаций «var1, var2, var3», «var1, var2, var5», «var1, var3, var7».
Может быть способ получше, но вот метод грубой силы
combs <- lapply(seq(ncol(df)), function(x) combn(names(df), x, simplify = F))
keys <- list()
for(i in seq_along(combs)){
keys[[i]] <- combs[[i]][sapply(combs[[i]], function(x) nrow(distinct(df[x])) == nrow(df))]
if (length(keys[[i]])) stop(paste('Found key of', i, 'columns, stopping'))
}
keys
# [[1]]
# list()
#
# [[2]]
# [[2]][[1]]
# [1] "var1" "var6"
#
# [[2]][[2]]
# [1] "var4" "var6"
#
# [[2]][[3]]
# [1] "var4" "var7"
Возможный подход:
library(dplyr)
lst <- c()
for (i in 2:ncol(df)) {
lst_combinations <- combn(names(df), i ,simplify=FALSE)
lst <- c(lst, lst_combinations)
}
lst_results <- c()
for (i in 1:length(lst)) {
nms <- lst[i][[1]]
lgth_df <- df %>% .[, colnames(.) %in% nms] %>% distinct() %>% count()
if (lgth_df$n == nrow(df)) {
nms <- paste(nms, collapse = ", ")
lst_results <- c(lst_results, nms)
}
}
Первые несколько комбинаций (всего найдено 80 для вашего примера):
[1] "var1, var6"
[2] "var4, var6"
[3] "var4, var7"
[4] "var1, var2, var3"
[5] "var1, var2, var5"
[6] "var1, var2, var6"
[7] "var1, var2, var7"
[8] "var1, var3, var6"
[9] "var1, var3, var7"
[10] "var1, var4, var6"
Спасибо! Можем ли мы попробовать использовать purrr, передать все комбинации имен в select ()%>% n_distinct () и получить результат в фрейме данных, чтобы один столбец содержал все возможные комбинации имен переменных, а другой столбец содержал значения n_distinct. Здесь нам может понадобиться помощь rlang.
Вот метод грубой силы, который перечисляет все возможные комбинации переменных. Кажется, есть 80 возможных комбинаций, соответствующих вашим критериям.
>df
var1 var2 var3 var4 var5 var6 var7
1 A C 1 5 AA AB 1
2 A C 2 5 BB AC 2
3 A D 1 7 AA BC 2
4 A D 2 3 BB CC 1
5 B C 1 8 AA AB 1
6 B C 2 6 BB AC 2
7 B D 1 9 AA BC 2
8 B D 2 6 BB CC 1
>n<-ncol(df)
>combinations<-unlist(lapply(1:n, function(x) unlist(apply(combn(n,x),2,list), recursive=F) ), recursive=F)
>length(combinations)
[1] 127
>count_distinct<-sapply(combinations, function(x){ nrow(unique(df[,x,drop=F])) } )
>length(which(count_distinct==8))
[1] 80
>combinations[which(count_distinct==8)]
[[1]]
[1] 1 6
[[2]]
[1] 4 6
[[3]]
[1] 4 7
[[4]]
[1] 1 2 3
[[5]]
[1] 1 2 5
[[6]]
[1] 1 2 6
[[7]]
[1] 1 2 7
[[8]]
[1] 1 3 6
[[9]]
[1] 1 3 7
...
Можно ли использовать имена переменных вместо их номеров?
Небольшая вариация других ответов, но вот запрошенный табличный вывод:
nms <- unlist(lapply(seq_len(length(df)), combn, x=names(df), simplify=FALSE), rec=FALSE)
out <- data.frame(
vars = vapply(nms, paste, collapse = ",", FUN.VALUE=character(1)),
counts = vapply(nms, function(x) nrow(unique(df[x])), FUN.VALUE=numeric(1))
)
Затем возьмите наименьшее количество переменных, которые должны быть первичным ключом:
out[match(nrow(df), out$counts),]
# vars counts
#12 var1,var6 8
Очень элегантное решение!
Фильтрация счетчика по количеству строк в основном фрейме данных, а затем по минимальному количеству используемых переменных должна выбрать одного из лучших кандидатов для первичного ключа. Итак ... предлагаю небольшое изменение.
Давайте оставим var4, var6 и var4, var6 также в выходных данных, поскольку оба обеспечивают наименьшее количество переменных, необходимых для использования в качестве первичного ключа.
Заимствуя полностью из ответа thelatemail и преобразовывая его в purrr:
library(tidyverse)
m_in_comb <- seq_len(length(df))
var_combs_listoflist <- map(m_in_comb, ~combn(x=names(df), m = .x, simplify=F)) %>%
unlist(recursive = F)
var_combs_listofchr <- map_chr(var_combs_listoflist, ~paste(.x, collapse = ","))
distinct_obs_per_var_comb = map_int(var_combs_listoflist, ~(select(df, .x) %>% n_distinct()))
keys <- tibble(var_combs = var_combs_listofchr, distinct_count = distinct_obs_per_var_comb)
primarykeys <- keys %>%
filter(distinct_count==nrow(df)) %>%
mutate(n_vars = str_count(var_combs, ",")+1) %>%
filter(n_vars==min(n_vars))
Спасибо! Можем ли мы попробовать использовать purrr, передать все комбинации имен в select ()%>% n_distinct () и получить результат в фрейме данных, чтобы один столбец содержал все возможные комбинации имен переменных, а другой столбец содержал значения n_distinct. Здесь нам может понадобиться помощь rlang.