我想在数据帧中找到与对象关联的最小值。数据框包含两列,表示对象的所有组合,以及每个组合的值列。它看起来像这样:
id_A id_B dist
206 208 2385.5096
207 208 467.8890
207 209 576.4631
...
208 209 1081.539
208 210 8214.439
...
我尝试了以下推荐的dplyr
功能:
df %>%
group_by(id_A) %>%
slice(which.min(dist))
但它不会产生所需的输出:
id_A id_B dist
...
207 208 467.8890
208 209 1081.5393
...
请注意,对于id 208,ID为207的组合具有最低值,但与id 208无关(当它在grouped_by
列中时)。
我写了一个正确的功能,但是因为我有很多条目,所以这是慢的方法。它是一个循环,通过包含特定id的所有条目对数据进行子集化,然后在该子集中找到最小值,并将该值与该id相关联。
你有什么想法,如何做到这一点,例如使用dplyr
。
答案 0 :(得分:0)
问题归结为需要一个长(而不是宽)的数据格式。首先,这里有一些可重现的数据(使用来自dplyr
的管道):
df <-
LETTERS[1:4] %>%
combn(2) %>%
t %>%
data.frame() %>%
mutate(val = 1:n()) %>%
setNames( c("id_A", "id_B", "dist") )
给出:
id_A id_B dist
1 A B 1
2 A C 2
3 A D 3
4 B C 4
5 B D 5
6 C D 6
我们想要的是一对列,使每个类别与其行的距离匹配。为此,我使用gather
中的tidyr
。它创建了新的列,告诉我们数据来自哪个列以及保留的值。在这里,我们告诉它从列id_A
和id_B
中提取每个ID条目的类别(然后根据需要复制dist
列)
df %>%
gather(whichID, Category, id_A, id_B)
给出
dist whichID Category
1 1 id_A A
2 2 id_A A
3 3 id_A A
4 4 id_A B
5 5 id_A B
6 6 id_A C
7 1 id_B B
8 2 id_B C
9 3 id_B D
10 4 id_B C
11 5 id_B D
12 6 id_B D
然后,我们可以将 data.frame传递给group_by
,然后使用summarise
向我们提供我们想要的任何信息。我知道你没有要求最大值,但我只是为了展示你可以用来得到你想要的任何类型结果的一般语法来包括它:
df %>%
gather(whichID, Category, id_A, id_B) %>%
group_by(Category) %>%
summarise(minDist = min(dist)
, maxDist = max(dist))
返回:
Category minDist maxDist
<chr> <int> <int>
1 A 1 3
2 B 1 5
3 C 2 6
4 D 3 6
我只是看了一下这个问题并意识到你想要显示哪个比较具有最小值。这是一种方法,通过跟踪匹配的索引(以便在gather
时复制它)然后从原始df
中提取正确的行并将两个比较值粘贴在一起来执行此操作:
df %>%
mutate(whichComparison = 1:n()) %>%
gather(whichID, Category, id_A, id_B) %>%
group_by(Category) %>%
summarise(minDist = min(dist)
, whichMin = whichComparison[which.min(dist)]
, maxDist = max(dist)
, whichMax = whichComparison[which.max(dist)]) %>%
mutate(
minComp = sapply(whichMin, function(x){
paste(df[x, "id_A"], df[x, "id_B"], sep = " vs " )})
, maxComp = sapply(whichMax, function(x){
paste(df[x, "id_A"], df[x, "id_B"], sep = " vs " )})
)
返回
Category minDist whichMin maxDist whichMax minComp maxComp
<chr> <int> <int> <int> <int> <chr> <chr>
1 A 1 1 3 3 A vs B A vs D
2 B 1 1 5 5 A vs B B vs D
3 C 2 2 6 6 A vs C C vs D
4 D 3 3 6 6 A vs D C vs D
如果你真的想要一个列给出哪个比较给出了最小值(以及输出中的最大值),你可以改为使用索引同时从id_A
和id_B
中提取原始df
,删除与感兴趣的类别匹配的那个,然后使用包use_first_valid_of
中的janitor
来抓住您感兴趣的那个。因为这会产生大量的中间列,我使用select
来清理备份:
df %>%
mutate(whichComparison = 1:n()) %>%
gather(whichID, Category, id_A, id_B) %>%
group_by(Category) %>%
summarise(minDist = min(dist)
, maxDist = max(dist)
, whichMin = whichComparison[which.min(dist)]
, whichMax = whichComparison[which.max(dist)]) %>%
mutate(
minA = df$id_A[whichMin]
, minB = df$id_B[whichMin]
, maxA = df$id_A[whichMax]
, maxB = df$id_B[whichMax]
) %>%
mutate_each(funs(ifelse(. == Category, NA, as.character(.)) )
, minA:maxB) %>%
mutate(minComp = use_first_valid_of(minA, minB)
, maxComp = use_first_valid_of(maxA, maxB)) %>%
select(-(whichMin:maxB))
返回:
Category minDist maxDist minComp maxComp
<chr> <int> <int> <chr> <chr>
1 A 1 3 B D
2 B 1 5 A D
3 C 2 6 A D
4 D 3 6 A C
另一种方法是首先将距离对转换为矩阵。在这里,我首先以相反的顺序复制比较,以确保矩阵完整(使用tidyr
到spread
):
bind_rows(
df
, rename(df, id_A = id_B, id_B = id_A)
) %>%
spread(id_B, dist)
返回:
id_A A B C D
1 A NA 1 2 3
2 B 1 NA 4 5
3 C 2 4 NA 6
4 D 3 5 6 NA
然后,我们只是跨越行应用,就像我们使用距离矩阵(可能是您的数据实际开始的位置)一样:
bind_rows(
df
, rename(df, id_A = id_B, id_B = id_A)
) %>%
spread(id_B, dist) %>%
mutate(
minDist = apply(as.matrix(.[, -1]), 1, min, na.rm = TRUE)
, minComp = names(.)[apply(as.matrix(.[, -1]), 1, which.min) + 1]
, maxDist = apply(as.matrix(.[, -1]), 1, max, na.rm = TRUE)
, maxComp = names(.)[apply(as.matrix(.[, -1]), 1, which.max) + 1]
) %>%
select(Category = `id_A`
, minDist:maxComp)
返回:
Category minDist minComp maxDist maxComp
1 A 1 B 3 D
2 B 1 A 5 D
3 C 2 A 6 D
4 D 3 A 6 C