Я строю большие направленные графы (используя igraph из R) и обнаружил странную проблему, в которой вершины явно дублируются для определенных имен вершин. Эта проблема не возникает в небольших графах, и проблема возникает только тогда, когда имена вершин достигают 1e + 05. Существует явная закономерность дублирования вершин. Чтобы забегать вперед, дублирование вершины выглядит так (сгенерировано в разделе 2 приведенного ниже кода):

    name_vertex id_cell id_vertex
 1:      100000  100000     97355
 2:       1e+05  100000   1435205
 3:      200000  200000    197106
 4:       2e+05  200000   1435206
 5:      400000  400000    396605
 6:       4e+05  400000   1435207
 7:      500000  500000    496356
 8:       5e+05  500000   1435208
 9:      700000  700000    695855
10:       7e+05  700000   1435209
11:      800000  800000    795606
12:       8e+05  800000   1435210
13:     1000000 1000000    995105
14:       1e+06 1000000   1435211

Дублирование происходит, когда достигается 1e + 05, а затем для этой и каждой последующей вершины генерируются дубликаты, то есть xe + 0n, где x находится в 1: 9, а n равно> = 5 (обратите внимание, что в этом графе нет 3e + 05 значная вершина по построению - она ​​лежит на краю матрицы - и поэтому ее нет).

Все x0 .. версии вершин содержат исходящие ребра, а версии xe + 0 .. содержат входящие ребра.


Воспроизводимый пример: (примечание: способ, которым я генерирую фрейм данных смежности, больше связан с конвейером, который я использовал для генерации графиков для моего варианта использования. Вероятно, проблема могла быть сгенерирована более напрямую).

Приведенный ниже код генерирует матрицу, идентифицирует смежности каждой ячейки, а затем строит из них граф. Ячейкам на краю матрицы присваиваются значения 0, чтобы удалить их из таблицы смежности (чтобы предотвратить закругление краев).

Есть три раздела:

(1) работа для размера матрицы 100x100: правильное поведение

(2) работа для размера матрицы 1200x1200: дублирование

(3) распаковка проблемы дублирования

ПРИМЕЧАНИЕ: для построения графика в (2) требуется около 30 секунд и 3-4 ГБ ОЗУ.

# packages
library(data.table); library(igraph)

# function to get adjacent cells in a matrix
get_adjacent <- function(cells, n_row, n_col) {
    adjacencies_i <- c(cells-n_row - 1, 
                       cells-n_row, 
                       cells-n_row+1, 
                       cells-1, 
                       cells+1, 
                       cells+n_row-1, 
                       cells+n_row, 
                       cells+n_row+1)
    return(adjacencies_i)
}

# function to get the margins of a matrix (i.e. 1-deep outer margin of cells)    
get_margins <- function(matrix) {
    dims <- dim(matrix)
    bottom_right <- prod(dims)
    top_right <- (bottom_right - dims[1])
    c(1:dims[1], # first column 
      top_right:bottom_right, # last column
      seq(1, top_right, dims[1]), # top row
      seq(dims[1], bottom_right, dims[1])) # bottom row
}


# (1) Before creating the failure case, produce a much smaller graph that 
# has the correct behaviour

# generate a matrix of 1-valued cells
test_mat <- matrix(1, ncol=100, nrow=100)

# remove edge cells to prevent the adjacencies wrapping around the edges
test_mat[get_margins(test_mat)] <- 0

# plot: all black cells are those that should be represented in the graph, and 
# each of these cells should each be linked to their immediately adjacent neighbours 
# (including diagonals - see get_adjacent function)
image(test_mat, asp=1, col=c("red", "black"))

# calculate the adjacency dataframe to calculate a graph from
permitted_cells <- which(test_mat[] == 1)
n_row <- dim(test_mat)[1]
n_col <- dim(test_mat)[2]

# full set of adjacencies
adj <- data.table(from = rep(permitted_cells, (1*2 + 1)^2 - 1), 
                  to = get_adjacent(permitted_cells, n_row, n_col))
# remove those that are 0-valued
adj_permitted <- adj[to %in% permitted_cells,] 

# calculate graph
g <- graph_from_data_frame(adj_permitted[,list(from, to)], directed = T)

# get vertex names 
vertex_names <- names(V(g))
graph_vertices <- data.table(name_vertex = vertex_names, 
                             id_cell = as.integer(vertex_names), 
                             id_vertex = 1:length(vertex_names)) 
setorder(graph_vertices, id_cell)

# looks good: same number of vertices in graph as there are 1-valued cells in the 
# original matrix
print(paste0("n_vertices: ", nrow(graph_vertices)))
print(paste0("n_cells: ", sum(test_mat)))


## (2) failure case. Code is identical to the above, save for the dimensions of 
## the matrix being much larger (1200 rather than 100), and the image() function 
## is commented out. 

# generate a matrix of 1-valued cells
test_mat <- matrix(1, ncol=1200, nrow=1200)

# remove edge cells to prevent the adjacencies wrapping around the edges
test_mat[get_margins(test_mat)] <- 0

# plot: all black cells are those that should be represented in the graph, and 
# each of these cells should each be linked to their immediately adjacent neighbours 
# (including diagonals - see get_adjacent function)
# image(test_mat, asp=1, col=c("red", "black"))

# calculate the adjacency dataframe to calculate a graph from
permitted_cells <- which(test_mat[] == 1)
n_row <- dim(test_mat)[1]
n_col <- dim(test_mat)[2]

# full set of adjacencies
adj <- data.table(from = rep(permitted_cells, (1*2 + 1)^2 - 1), 
                  to = get_adjacent(permitted_cells, n_row, n_col))
# remove those that are 0-valued
adj_permitted <- adj[to %in% permitted_cells,] 

# calculate graph
g <- graph_from_data_frame(adj_permitted[,list(from, to)], directed = T)

# get vertex names 
vertex_names <- names(V(g))
graph_vertices <- data.table(name_vertex = vertex_names, 
                             id_cell = as.integer(vertex_names), 
                             id_vertex = 1:length(vertex_names)) 
setorder(graph_vertices, id_cell)

# there are 7 more vertices than there are 1-valued cells
print(paste0("n_vertices: ", nrow(graph_vertices)))
print(paste0("n_cells: ", sum(test_mat)))

print(paste0("n_extra_vertices: ", nrow(graph_vertices) - sum(test_mat)))

# (3) What are these extra vertices?
# get duplicated vertices
duplicated_vertices <- 
    graph_vertices[id_cell %in% graph_vertices[duplicated(id_cell),id_cell]]
setorder(duplicated_vertices, id_cell, id_vertex)

# the 7 additional vertices arise through duplication 
nrow(duplicated_vertices)
print(duplicated_vertices)

# xe+.. version has the incoming edges
incoming <- adjacent_vertices(g, duplicated_vertices$id_vertex, mode="in")
incoming[unlist(lapply(incoming, function(x) length(x) != 0))]
# x0.. version has outgoing edges
outgoing <- adjacent_vertices(g, duplicated_vertices$id_vertex, mode="out")
outgoing[unlist(lapply(outgoing, function(x) length(x) != 0))]

Чтобы (наконец) перейти к вопросу. Что здесь происходит? Что я могу сделать, чтобы предотвратить такое поведение? В настоящее время у меня есть обходной путь: взять входящие ребра, полученные версией xe + 0 .., и добавить ребра к этим вершинам для версии x0 .. перед удалением версии вершины xe + 0 ...

0
Simon Mills 16 Окт 2020 в 15:49

1 ответ

Лучший ответ

Проблема, похоже, вызвана тем, что R (или igraph) приравнивает две формы 100000 и 1e+05. Мне удалось решить эту проблему, добавив в начало скрипта оператор options(scipen=99), который запрещает R использовать нотацию e.

0
Andrew Gustar 21 Окт 2020 в 10:47