data frame ggplot2
Iterativa y jerárquicamente ciclo a través de filas hasta que se cumpla una condición (3)
Estoy tratando de resolver un problema de administración de datos en R.
Supongamos que mi información se ve de la siguiente manera:
id <- c("123", "414", "606")
next.up <- c("414", "606", "119")
is.cond.met <- as.factor(c("FALSE", "FALSE", "TRUE"))
df <- data.frame(id, next.up, is.cond.met)
> df
id next.up is.cond.met
1 123 414 FALSE
2 414 606 FALSE
3 606 119 TRUE
Y me gustaría obtener es lo siguiente:
id <- c("123", "414", "606")
next.up <- c("414", "606", "119")
is.cond.met <- as.factor(c("FALSE", "FALSE", "TRUE"))
origin <- c("606", "606", "119")
df.result <- data.frame(id, next.up, is.cond.met, origin)
> df.result
id next.up is.cond.met origin
1 123 414 FALSE 606
2 414 606 FALSE 606
3 606 119 TRUE 119
En otras palabras: quiero hacer coincidir cada ID con su "origen" cuando una condición dada (is.met) es verdadera. La dificultad que tengo es que esto es iterativo y jerárquico: para encontrar el origen puedo tener que atravesar múltiples grados de separaciones. los pasos lógicos se ilustran a continuación. Realmente no estoy seguro de cómo abordar esto en R.
ACTUALIZAR
Uno de los comentarios propone una solución de data.frame que funciona para datos ordenados, como en el ejemplo mínimo anterior. En verdad, mis datos no están ordenados de esa manera. Un mejor ejemplo es el siguiente:
id <- c("961980", "14788", "902460", "900748", "728912", "141726", "1041190", "692268")
next.up <- c("20090", "655036", "40375164", "40031850", "40368996", "961980", "141726", "760112")
is.cond.met <- c(TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE)
df <- data.frame(id, next.up, is.cond.met, stringsAsFactors = FALSE)
glimpse(df)
Observations: 8
Variables: 3
$ id <chr> "961980", "14788", "902460", "900748", "728912", "141726", "1041190", "692268"
$ next.up <chr> "20090", "655036", "40375164", "40031850", "40368996", "961980", "141726", "760112"
$ is.cond.met <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE
> df
id next.up is.cond.met
1 961980 20090 TRUE
2 14788 655036 FALSE
3 902460 40375164 FALSE
4 900748 40031850 FALSE
5 728912 40368996 FALSE
6 141726 961980 FALSE
7 1041190 141726 FALSE
8 692268 760112 FALSE
ACTUALIZACIÓN 2 : el resultado final debería verse así:
> df.end.result
id next.up is.cond.met origin
1 961980 20090 TRUE <NA>
2 14788 655036 FALSE <NA>
3 902460 40375164 FALSE <NA>
4 900748 40031850 FALSE <NA>
5 728912 40368996 FALSE <NA>
6 141726 961980 FALSE 961980
7 1041190 141726 FALSE 961980
8 692268 760112 FALSE <NA>
Entonces, imho, creo que no puedes resolverlo sin una actualización interactiva.
Similar a @ procrastinatus-maximus aquí hay una solución iterativa con dplyr
library(dplyr)
dfIterated <- data.frame(df, cond.origin.node = id,
cond.update = is.cond.met, stringsAsFactors = F)
initial.cond <- dfIterated$is.cond.met
while(!all(dfIterated$is.cond.met %in% c(TRUE, NA))) {
dfIterated <- dfIterated %>%
mutate(cond.origin.node = if_else(is.cond.met,
cond.origin.node,
next.up),
parent.match = match(next.up, id),
cond.update = (cond.update[parent.match] | cond.update),
cond.origin.node = if_else(!is.cond.met & cond.update,
next.up[parent.match],
next.up),
is.cond.met = cond.update)
}
# here we use ifelse instead of if_else since it is less type strict
dfIterated %>%
mutate(cond.origin.node = ifelse(initial.cond,
yes = NA,
no = cond.origin.node))
editar : condición de inicio añadida; reemplazado dplyr::if_else
por dplyr::if_else
Explicación : actualizamos iterativamente el dfIterated
para incluir todos los nodos next.up
como ya se sugirió. Aquí lo hacemos para cada id
en paralelo.
- Cambiamos
cond.origin.node
y lo reemplazamos por id sicond.is.met == TRUE
y connext.up
"de lo contrario" - los valores deNA
encond.is.met
devolverán los valores deNA
sí mismos, lo cual es muy práctico en nuestro caso.- Luego calculamos el índice parental correspondiente
- Actualizamos el
cond.update
donde hacemos coincidir el padre en la columnaid
. (Los valores que devolverán NA, es decir, no hay coincidencias en laid
, serán reemplazados porNA
.) Y usamos|
(o) operador que fortunetaley devolveráTRUE == (TRUE | NA)
si hay entradaTRUE
anterior encond.update
- Entonces necesitamos calcular el nodo de origen para la condición
TRUE
. - Y luego upde la condición en
is.cond.met
- Repite todo hasta que nuestro
is.cond.met
consista únicamente enTRUE
s oNA
s. El orgin contendrá nodos para los que elcond.is.met == TRUE
El resultado del ejemplo anterior se ve así:
> dfIterated
id next.up is.cond.met cond.origin.node cond.update
1 961980 20090 TRUE <NA> TRUE
2 14788 655036 NA <NA> NA
3 902460 40375164 NA <NA> NA
4 900748 40031850 NA <NA> NA
5 728912 40368996 NA <NA> NA
6 141726 961980 TRUE 961980 TRUE
7 1041190 141726 TRUE 961980 TRUE
8 692268 760112 NA <NA> NA
¡Espero que esto ayude! Una búsqueda hacia adelante funcionaría de manera similar. Las mejoras adicionales dependen del tipo de resultados que desee conservar (por ejemplo, ¿realmente desea sobrescribir is.cond.met
?)
Espero haber entendido tu problema correctamente y aquí sigue mi punto de vista. Parece que intentas resolver un problema de red en términos de tablas de datos. Sugiero la siguiente formulación.
Tenemos una red, definida como un conjunto de bordes (las columnas id
y next.up
corresponden a vertex_from
y vertex_to
). La red es un conjunto de árboles. La columna is.cond.met
mapea vértices que son puntos finales o las raíces de los árboles. Los árboles con raíz no asignada no se tienen en cuenta.
He modificado ligeramente tu MRE para hacerlo más demostrativo.
id <- c("961980", "14788", "902460", "900748", "728912", "141726", "1041190", "692268", "40368996", "555555", "777777")
next.up <- c("20090", "655036", "40375164", "40031850", "40368996", "961980", "141726", "760112", "692268", "760112", "555555")
is.cond.met <- c(TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE)
dt <- data.table(id, next.up, is.cond.met, stringsAsFactors = FALSE)
Ahora permitamos traducir todo al lenguaje de los gráficos.
library(data.table)
library(magrittr)
library(igraph)
graph_from_edgelist(as.matrix(dt[, 1:2, with = F])) -> dt_graph
V(dt_graph)$color <- ifelse(V(dt_graph)$name %in% dt[is.cond.met == T]$next.up, "green", "yellow")
E(dt_graph)$arrow.size <- .7
E(dt_graph)$width <- 2
plot(dt_graph, edge.color = "grey50")
Los vértices verdes son raíces mapeadas, llamémoslos treeroots. Sus vecinos del orden fisrt son las raíces de las grandes ramas principales de cada árbol, déjalos ser branchroots. El problema es que para cada columna de vértices en id
de los datos iniciales, averigüe la raíz branchroot correspondiente.
treeroots <- dt[is.cond.met == T]$next.up %>% unique
lapply(V(dt_graph)[names(V(dt_graph)) %in% treeroots],
function(vrtx) neighbors(dt_graph, vrtx, mode = "in")) -> branchroots
Podemos encontrar todos los vértices descendiendo a cada branchroot con la ayuda de la función ego
del paquete igraph
.
lapply(seq_along(branchroots), function(i) {
data.table(tree_root = names(branchroots[i]), branch_root = branchroots[[i]]$name)
}) %>% rbindlist() -> branch_dt
branch_dt[, trg_vertices := ego(dt_graph, order = 1e9,
V(dt_graph)[names(V(dt_graph)) %in% branch_dt$branch_root],
mode = "in", mindist = 1) %>% lapply(names)]
branch_dt
# tree_root branch_root trg_vertices
# 1: 20090 961980 141726,1041190
# 2: 760112 692268 40368996,728912
# 3: 760112 555555 777777
Después de eso, podemos crear la columna de origin
.
sapply(seq_along(branch_dt$branch_root),
function(i) rep(branch_dt$branch_root[i],
length(branch_dt$trg_vertices[[i]]))) %>% unlist -> map_vertices
branch_dt$trg_vertices %>% unlist() -> map_names
names(map_vertices) <- map_names
dt[, origin := NA_character_]
dt[id %in% map_names, origin := map_vertices[id]]
dt
# id next.up is.cond.met origin
# 1: 961980 20090 TRUE NA
# 2: 14788 655036 FALSE NA
# 3: 902460 40375164 FALSE NA
# 4: 900748 40031850 FALSE NA
# 5: 728912 40368996 FALSE 692268
# 6: 141726 961980 FALSE 961980
# 7: 1041190 141726 FALSE 961980
# 8: 692268 760112 TRUE NA
# 9: 40368996 692268 FALSE 692268
# 10: 555555 760112 FALSE NA
# 11: 777777 555555 FALSE 555555
Por conveniencia, arreglé el código resultante en una función.
add_origin <- function(dt) {
require(data.table)
require(magrittr)
require(igraph)
setDT(dt)
graph_from_edgelist(as.matrix(dt[, .(id, next.up)])) -> dt_graph
treeroots <- dt[is.cond.met == T]$next.up %>% unique
lapply(V(dt_graph)[names(V(dt_graph)) %in% treeroots],
function(vrtx) neighbors(dt_graph, vrtx, mode = "in")) -> branchroots
lapply(seq_along(branchroots), function(i) {
data.table(tree_root = names(branchroots[i]), branch_root = branchroots[[i]]$name)
}) %>% rbindlist() -> branch_dt
branch_dt[, trg_vertices := rep(list(NA), nrow(branch_dt))][]
vertices_on_branch <- ego(dt_graph, order = 1e9,
V(dt_graph)[names(V(dt_graph)) %in% branch_dt$branch_root],
mode = "in", mindist = 1) %>% lapply(names)
set(branch_dt, j = "trg_vertices", value = list(vertices_on_branch))
sapply(seq_along(branch_dt$branch_root),
function(i) rep(branch_dt$branch_root[i],
length(branch_dt$trg_vertices[[i]]))) %>% unlist -> map_vertices
branch_dt$trg_vertices %>% unlist() -> map_names
names(map_vertices) <- map_names
dt[, origin := NA_character_]
dt[id %in% map_names, origin := map_vertices[id]]
dt[]
}
Para su MRE produce la salida deseada.
df0 <- data.frame(id = c("961980", "14788", "902460", "900748", "728912", "141726", "1041190", "692268"),
next.up = c("20090", "655036", "40375164", "40031850", "40368996", "961980", "141726", "760112"),
is.cond.met = c(TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE), stringsAsFactors = FALSE)
df0 %>% add_origin
# id next.up is.cond.met origin
# 1: 961980 20090 TRUE NA
# 2: 14788 655036 FALSE NA
# 3: 902460 40375164 FALSE NA
# 4: 900748 40031850 FALSE NA
# 5: 728912 40368996 FALSE NA
# 6: 141726 961980 FALSE 961980
# 7: 1041190 141726 FALSE 961980
# 8: 692268 760112 FALSE NA
El enfoque descrito debería ser significativamente más rápido que la actualización iterativa de un data.frame
dentro de un bucle.
He ampliado un poco los datos de ejemplo para mostrar lo que sucede con más valores TRUE
en is.cond.met
. Usando el paquete data.table
, podrías hacer:
library(data.table)
setDT(df)[, grp := shift(cumsum(is.cond.met), fill=0)
][, origin := ifelse(is.cond.met, next.up, id[.N]), by = grp][]
lo que da:
> df
id next.up is.cond.met grp origin
1: 123 414 FALSE 0 606
2: 414 606 FALSE 0 606
3: 606 119 TRUE 0 119
4: 119 321 FALSE 1 321
5: 321 507 TRUE 1 507
6: 507 185 TRUE 2 185
Explicación:
-
shift(cumsum(is.cond.met), fill=0)
una variable de agrupación conshift(cumsum(is.cond.met), fill=0)
. - Con
ifelse(is.cond.met, next.up, id[.N])
asigna los valores correctos alorigin
.
Nota: Las columnas id
y next.up
deberían ser de carácter de clase para que funcione lo anterior (por esa razón utilicé stringsAsFactors = FALSE
en la construcción de los datos de ejemplo extendidos). Si son factores, as.character
primero con as.character
. Si is.cond.met
no es ya una lógica, as.logical
con as.logical
.
En los datos de ejemplo actualizados, el código anterior ofrece:
id next.up is.cond.met grp origin
1: 961980 20090 TRUE 0 20090
2: 14788 655036 FALSE 1 692268
3: 902460 40375164 FALSE 1 692268
4: 900748 40031850 FALSE 1 692268
5: 728912 40368996 FALSE 1 692268
6: 141726 961980 FALSE 1 692268
7: 1041190 141726 FALSE 1 692268
8: 692268 760112 FALSE 1 692268
Datos usados:
id <- c("123", "414", "606", "119", "321", "507")
next.up <- c("414", "606", "119", "321", "507", "185")
is.cond.met <- c(FALSE, FALSE, TRUE, FALSE, TRUE, TRUE)
df <- data.frame(id, next.up, is.cond.met, stringsAsFactors = FALSE)