R/graphNEL.R

Defines functions inE inOutCounts .dropEdges edgeKiller graphNEL_init_edges graphNEL_init_edgeL_weights graphNEL_init_edges_nested validGraph

Documented in validGraph

validGraph<-function(object, quietly=FALSE) {
    ## FIXME: we are doing if/else branching here on
    ## specific subclasses of graph.  We should make this a generic
    ## so we can organize checking.  Also, graphAM is not
    ## currently being checked in any way.
    bad = FALSE
    if (is(object, "graphNEL")) {
        objEdges<-edges(object)
        objNodes<-nodes(object)
        if (any(is.na(objNodes))) {
            if (!quietly ) cat("NA element in nodes.\n")
            bad <- TRUE
        }
        if(length(objEdges)>0)
          if(any(is.na(unlist(objEdges,use.names=FALSE)))) {
              if(!quietly)
                cat("NA element in edges.\n")
              bad <- TRUE
          }

        ##don't think we want to force this one
        ##      if (length(objNodes)!=length(objEdges)) {
        ##          if( !quietly )
        ##              cat("Nodes and edges must have the same length.\n")
        ##          bad <- TRUE
        ##      }
        if (!all( names(objEdges) %in% objNodes )) {
            if( !quietly )
              cat("Edges don't have the same names as the nodes.\n")
            bad <- TRUE
        }
        if (any(duplicated(objNodes))) {
            if( !quietly )
              cat("Node names may not be duplicated\n")
            bad <- TRUE
        }
        ##check for reciprocity in undirected graphs
        ##paste to->from and from->to if any are not duplicated then
        ##the edge is not reciprocal. Note we are not going to handle
        ##multiedges well.
        if(edgemode(object) == "undirected" && length(objEdges)>0 ) {
            fr <- rep(names(objEdges), sapply(objEdges, length))
            to <- unlist(objEdges)
            frto <- paste(fr, to, sep=EDGE_KEY_SEP)
            tofr <- paste(to, fr, sep=EDGE_KEY_SEP)
            badEdges <- setdiff(tofr, frto)
            if (length(badEdges) > 0) {
                if (!quietly) {
                    cat("the graph is undirected and the following edges",
                        "are not reciprocated:\n", pasteq(badEdges), "\n\n")
                }
                bad <- TRUE
            }
        }
    }
    else if( is(object, "distGraph") ) {
        if( is(object@Dist, "dist") )
          return(TRUE)
        else
          return(FALSE)
    }
    return(!bad)
}

setMethod("initialize", "graphNEL",
     function(.Object, ...) {
        .Object <- callNextMethod()
        validObject(.Object)
        return(.Object)
     })


graphNEL_init_edges_nested <- function(nodes, edgeL) {
    if(length(nodes) != length(edgeL) )
      stop("'nodes' and 'edgeL' must have same length")
    nameE <- names(edgeL)
    if( !is.null(nameE) && !all( nameE %in% nodes) )
      stop("'edgeL' names must agree with 'nodes'")
    if( !is.null(nameE) )
      edgeL <- edgeL[nodes]
    edgeL <- lapply(edgeL, function(x) {
        if (is.character(x$edges))
          x$edges <- match(x$edges, nodes)
        if (is.null(x) || is.null(x$edges))
          x <- list(edges=numeric(0))
        x
    })
    edgeL
}


graphNEL_init_edgeL_weights <- function(gnel) {
    defaultWeight <- 1
    edgeDataDefaults(gnel, attr="weight") <- defaultWeight
    edgeL <- gnel@edgeL
    wts <- unlist(lapply(edgeL, function(x) {
        w <- x$weights
        if (is.null(w) || length(w) == 0)
          return(rep(defaultWeight, length(x$edges)))
        w
    }))
    if (!is.numeric(wts))
      stop("weights in edgeL must be numeric")

    eSpec <- .getAllEdges(gnel)
    from <- eSpec$from
    to <- eSpec$to
    edgeData(gnel, from=from, to=to, attr="weight") <- wts
    ## remove weights, since now stored in the edgeData
    edgeL <- lapply(edgeL, function(x) x["edges"])
    gnel@edgeL <- edgeL
    gnel
}



graphNEL_init_edges <- function(nodes, edges) {
    nameE <- names(edges)
    if (is.null(nameE) || !all(nameE %in% nodes))
        stop("'edges' names must agree with 'nodes'")
    if (any(unlist(lapply(edges, is.list))))
        stop("'edges' must be list of character()")
    ##merge all edges with same names
    if(any(duplicated(nameE))){
        edges <- split(edges, nameE)
        edges <- lapply(edges, unlist)
        nameE <- names(edges)
    }
    ##melt list
    m <- sapply(edges, length)
    n <- m > 0
    els <- edges[n]
    edgeF <- rep(names(els), m[n])
    edgeT <- unlist(els)
    edgeL <- unname(cbind(edgeF, edgeT))
    if(sum(n)>0){
        eL <- match(edgeL[, 2], nodes)
        edgeL <- split(eL, edgeL[, 1])
        edgeL <- c(edgeL, lapply(edges[!n], function(x) integer(0)))
    }else{
        edgeL <- lapply(edges, function(x) integer(0))
    }
    edgeL <- edgeL[nameE]
    edgeL <- lapply(edgeL, function(x) list(edges=x))
    edgeL
}


setMethod("initialize", "graphNEL",
    function(.Object, nodes=character(0), edgeL, edgemode)
    ## FIXME: what about edge weights?
{
    if (length(nodes))
      checkValidNodeName(nodes)
    if( missing(edgemode) )
      edgemode <- "undirected"
    doWeights <- FALSE
    if (missing(edgeL) || (!is.null(edgeL) && length(edgeL) == 0)) {
        edgeL <- vector(mode="list", length=length(nodes))
        names(edgeL) <- nodes
    } else {
        ## which list structure was used?
        edgeParser <- graphNEL_init_edges
        firstVal <- edgeL[[1]]
        if (is.null(firstVal))
            stop("'edgeL' must be list of character or list of lists, got NULL")
        if (length(edgeL) > 0 && is.list(edgeL[[1]])) {
            edgeParser <- graphNEL_init_edges_nested
            doWeights <- TRUE
        }
        edgeL <- edgeParser(nodes, edgeL)
    }
    .Object@nodes <- nodes
    .Object@edgeL <- edgeL
    .Object@graphData$edgemode <- edgemode
    validObject(.Object)
    if (doWeights)
      .Object <- graphNEL_init_edgeL_weights(.Object)
    return(.Object)
})


##the graphNEL representation stores edges as indexes into the
##node label vector
setMethod("edges", "graphNEL", function(object, which) {
    edgeL <- object@edgeL
    if (!missing(which)) {
        if (!is.character(which))
            stop("'Nodes' must be missing or a character vector")
        edgeL <- edgeL[which]
    }
    gNodes <- nodes(object)
    lapply(edgeL, function(x) gNodes[x$edges])
})

setMethod("adj", c("graphNEL", "ANY"), function(object, index) {
    initI <- as.character(index)
    nd <- nodes(object)
    if( is.character(index) )
      index <- match(index, nd)
    bad_idx <- which(is.na(index) | index < 0L | index > length(nd))
    if( length(bad_idx) != 0L ) {
      what <- if( length(bad_idx) == 1L ) "vertex is" else "vertices are"
      in1string <- paste(sQuote(initI[bad_idx]), collapse=", ")
      stop(what, " not in graph: ", in1string)
    }
    edges(object)[index]})


setMethod("edgeL", "graphNEL", function(graph, index) {
    if( missing(index) )
      graph@edgeL
    else
      graph@edgeL[index]})


setMethod("subGraph", signature(snodes="character", graph="graphNEL"),
          function(snodes, graph) {
              origNodes <- nodes(graph)
              snodesIdx <- match(snodes, origNodes)
              if (any(is.na(snodesIdx))) {
                  bad <- snodes[which(is.na(snodesIdx))]
                  stop("'snodes' contains nodes not in graph: ",
                       pasteq(bad))

              }
              killedNodes <- origNodes[-snodesIdx]
              newEdges <- lapply(edges(graph)[snodes],
                                 function(x) {
                                     whD <- match(killedNodes, x, nomatch=0)
                                     if (any(whD))
                                       x[-whD]
                                     else
                                       x
                                 })
              ans <- graphNEL(nodes=snodes, edgeL=newEdges,
                         edgemode=edgemode(graph))
              ## FIXME: need to clean the attributes, right now we are passing
              ##        too much.
              nodeIdx <- match(snodes, names(graph@nodeData), 0)
              ans@nodeData@defaults <- graph@nodeData@defaults
              ans@nodeData@data <- graph@nodeData@data[nodeIdx]
              ee <- .getAllEdges(ans)
              if (length(ee$from) && length(ee$to)) {
                  kk <- .makeEdgeKeys(ee$from, ee$to)
                  whkk <- match(kk, names(graph@edgeData), 0)
                  ans@edgeData@defaults <- graph@edgeData@defaults
                  ans@edgeData@data <- graph@edgeData@data[whkk]
              }
              ans
          })


setMethod("numNodes", "graphNEL", function(object) length(object@nodes))


setMethod("addNode", signature(node="character", object="graphNEL",
                               edges="missing"),
          function(node, object, edges) {
              gN = nodes(object)
              already <- match(node, gN)
              if( any(!is.na(already)) )
                stop("node(s) already in graph: ", pasteq(gN[already]))
              checkValidNodeName(node)
              ## add them on the end so we don't renumber
              gN = c(gN, node)
              edgeL <-  object@edgeL
              nEd <- vector("list", length=length(node))
              names(nEd) <- node
              for(i in seq(along=nEd))
                nEd[[i]] <- list(edges=numeric(0))
              edgeL <- c(edgeL, nEd)
              object@nodes <- gN
              object@edgeL <- edgeL
              object
          })


##they need to supply a list of edges, one for each element of node
##it might be better to do this by first adding the nodes then
##calling addEdges on that graph
setMethod("addNode", signature(node="character", object="graphNEL",
                               edges="list"),
          function(node, object, edges) {
              ## first add the nodes, it does the checking too
              object <- addNode(node, object)
              ## now add the edges:
              if (!all(names(edges) == node))
                stop("'edges' must be named and in the same order as nodes")
              doWeights <- FALSE
              newEdges <- lapply(edges, function(x) {
                  if (is.character(x))
                    x
                  else if (is.numeric(x)) {
                      doWeights <<- TRUE  ## set flag in function scope
                      if (length(x) == 0)
                        enms <- character(0)
                      else
                        enms <- names(x)
                      if (is.null(enms))
                        stop("'edges' must be character or have names ",
                             "corresponding to nodes")
                      enms
                  } else {
                      stop("'edges' must be character or numeric list elements")
                  }
              })

              for (i in seq(along=newEdges)) {
                  if (length(newEdges[[i]]) == 0)
                    next
                  if (doWeights)
                   object <- addEdge(from=node[i], to=newEdges[[i]], object,
                                     weights=edges[[i]])
                  else
                    object <- addEdge(from=node[i], to=newEdges[[i]], object)
              }
              object
          })


setMethod("removeNode", c("character", "graphNEL"),
          function(node, object) {
              ##first clear the node -- does the checking too
              object <- clearNode(node, object)
              nN <- nodes(object)
              wh <- match(node, nN)
              gN <- nN[-wh]
              nE <- object@edgeL[-wh]
              ## Now renumber the nodes as stored in the edgelist
              nE2 <- lapply(nE, function(el) {
                  oldN <- nN[el$edges]
                  el$edges <- match(oldN, gN)
                  el
              })
              object@nodes <- gN
              object@edgeL <- nE2
              object
          })


setMethod("clearNode", c("character", "graphNEL"), function(node, object) {
    gN <- nodes(object)
    whN <- match(node, gN)
    if(any(is.na(whN)) )
      stop("'node' not in graph: ", pasteq(gN[is.na(whN)]))
    ## clear node attributes
    object <- clearNodeData(object, node)
    object <- .dropEdges(object, whN)
    object@edgeL[whN] <- list(list(edges=numeric(0)))
    object
})


edgeKiller <- function(edgeL, from, whichKill) {
    for (i in seq(along=from)) {
        toKill <- whichKill[[i]]
        toKill <- toKill[!is.na(toKill)]
        if (length(toKill) == 0)
          stop("no edge 'from' ", sQuote(from[i]), " to remove")
        edgeL[[from[i]]]$edges <- edgeL[[from[i]]]$edges[-toKill]
    }
    edgeL
}


setMethod("removeEdge",
          signature(from="character", to="character", graph="graphNEL"),
          function(from, to, graph) {
              gN <- nodes(graph)
              wh <- match(c(from, to), gN)
              if( any(is.na(wh)) )
                stop("'from' or 'to' not in graph: ",
                     pasteq(unique(wh[is.na[wh]])))
              if (length(to) == 1)
                to <- rep(to, length(from))
              if (length(from) == 1)
                from <- rep(from, length(to))
              if (!isDirected(graph)) {
                  fromOrig <- from
                  from <- c(fromOrig, to)
                  to <- c(to, fromOrig)
                  remove(fromOrig)
              }
              graph <- clearEdgeData(graph, from, to)
              remEL <- split(to, from)
              fromU <- names(remEL)
              nE <- edges(graph, fromU)
              whD <- mapply(function(x, y) match(x, y), remEL, nE,
                            SIMPLIFY=FALSE)
              graph@edgeL <- edgeKiller(graph@edgeL, fromU, whD)
              graph
          })


setMethod("addEdge", signature=signature(from="character", to="character",
                       graph="graphNEL", weights="numeric"),
          function(from, to, graph, weights) {
              graph <- addEdge(from, to, graph)
              if (!("weight" %in% names(edgeDataDefaults(graph))))
                edgeDataDefaults(graph, attr="weight") <- 1L
              edgeData(graph, from=from, to=to, attr="weight") <- weights
              graph
          })


setMethod("addEdge", signature=signature(from="character", to="character",
                       graph="graphNEL", weights="missing"),
          function(from, to, graph) {
              preEdges <- isAdjacent(graph, from, to)
              if (any(preEdges)) {
                  preFr <- from[preEdges]
                  preTo <- to[preEdges]
                  preEdges <- paste(preFr, preTo, sep=EDGE_KEY_SEP)
                  warning("edges replaced: ", pasteq(preEdges))
              }
              gN <- nodes(graph)
              whF <- match(from, gN)
              if( any(is.na(whF)) )
                stop("not a node: ", pasteq(from[is.na(whF)]))
              whT <- match(to, gN)
              if( any(is.na(whT)) )
                stop("not a node: ", pasteq(to[is.na(whT)]))
              ##roll out the shorter one
              lenT <- length(to)
              lenF <- length(from)
              if( lenT > lenF ) {
                  from <- rep(from, lenT)
                  whF <- rep(whF, lenT)
              }
              if( lenF > lenT ) {
                  whT <- rep(whT, lenF)
                  to <- rep(to, lenF)
              }
              ##now the same
              lenN <- max(lenT, lenF)
              eL <- graph@edgeL
              for(i in seq_len(lenN)) {
                  old <- eL[[from[i]]]
                  ## remove duplicate edges
                  old$edges <- unique(c(old$edges, whT[i]))
                  eL[[from[i]]] <- old
              }
              ##if undirected, then we need to go in both directions
              if( edgemode(graph) == "undirected")
                for(i in seq_len(lenN)) {
                    old <- eL[[to[i]]]
                    ## remove duplicate edges
                    old$edges <- unique(c(old$edges, whF[i]))
                    eL[[to[i]]] <- old
                }
              graph@edgeL <- eL
              ##FIXME: should we call validObject here?
              graph
          })



## Collapse a set of nodes and the corresponding edges
setMethod("combineNodes", c("character", "graphNEL", "character"),
          function(nodes, graph, newName, collapseFunction=sum) {
              if( length(newName) > 1 )
                stop("'newName' must have length 1")
              gN <- nodes(graph)
              whN <- match(nodes, gN)
              if( anyNA(whN) )
                stop("not a node: ", pasteq(nodes[is.na(whN)]))
              eL <- graph@edgeL
              outE <- eL[nodes]
              if( length(nodes) == 1 ) {
                  warning("nothing to collapse")
                  return(graph)
              }

              ##function to collapse weights for combined edges
              collapseFunction <- match.fun(collapseFunction)

              ##if undirected then we know everything
              inE <- if( edgemode(graph) == "directed" ) inEdges(nodes, graph) else NULL
              g2 <- removeNode(nodes, graph)
              g2 <- addNode(newName, g2)
              oE <- gN[unlist(lapply(outE[nodes], "[[", "edges"), use.names=FALSE)]
              oW <- unlist(edgeWeights(graph, nodes), use.names=FALSE)
              if (is.null(oW)) oW <- rep(1, length(oE))
              toW <- tapply(oW, oE, collapseFunction)[setdiff(unique(oE), nodes)]

              ##there might be no edges to add
              if(length(toW))
                  g2 <- addEdge(newName, names(toW), g2, as.numeric(toW))

              ##if directed we need to fix up the incoming edges
              if( !is.null(inE) )
              {
                  inE <- lapply(inE, setdiff, nodes)
                  inEl <- unique(unlist(inE), use.names=FALSE)
                  oW <- as.numeric(sapply(edgeWeights(graph, inEl), function(x)
                                          collapseFunction(x[intersect(names(x), nodes)])))
                  if(length(inEl))
                      g2 <- addEdge(inEl, newName, g2, oW)
              }
              g2
          })


##inEdges returns a list of all nodes with edges
##to the nodes in "node"
setMethod("inEdges", c("missing", "graphNEL"),
          function(node, object)
          inEdges(nodes(object), object))


##seems more sensible - if there is only one arg
setMethod("inEdges", c("graphNEL", "missing"),
          function(node, object)
          inEdges(nodes(node), node))


setMethod("inEdges", c("character", "graphNEL"), function(node, object) {
    gN <- nodes(object)
    whN <- match(node, gN)
    if( any(is.na(whN)) )
        stop("not a node: ", pasteq(node[is.na(whN)]))
    nN <- length(node)
    rval <- vector("list", length=nN)
    names(rval) <- node
    eL <- object@edgeL
    for (i in seq_len(nN)) {
        whOnes <- sapply(eL, function(x) {
            if (whN[i] %in% x$edges)
                return(TRUE)
            FALSE
        })
        rval[[i]] <- gN[whOnes]
    }
    rval
})


.dropEdges <- function(self, x) {
    ## Remove all edges in graphNEL self to node with
    ## index x.  Also remove all edges from node with index x.
    ## Return the modified graphNEL.
    ## Removing edges also removes the associated attributes.
    oldEdgeL <- self@edgeL
    newEdgeL <- vector(mode="list", length=length(oldEdgeL))
    names(newEdgeL) <- names(oldEdgeL)
    nds <- nodes(self)
    for (i in seq(along=nds)) {
        toList <- oldEdgeL[[i]]$edges
        if (i %in% x) {
            to <- nds[toList]
            if (length(to))
              self <- clearEdgeData(self, from=nds[i], to=to)
            toList <- list(edges=numeric(0))
        } else {
            bad <- match(x, toList)
            bad <- bad[!is.na(bad)]
            if (length(bad)) {
                self <- clearEdgeData(self, from=nds[i], to=nds[toList[bad]])
                toList <- list(edges=toList[-bad])
            } else {
                toList <- list(edges=toList)
            }
        }
        newEdgeL[[nds[i]]] <- (toList)
    }
    self@edgeL <- newEdgeL
    self
}

##a leaf is an element of the graph with in edges and no out
## edges - the edgeL list in a directed graphNEL list the out
##edges

inOutCounts <- function(object) {
   if(!(edgemode(object)) == "directed")
      stop("only for directed graphs")
   numOut=sapply(object@edgeL, function(x) length(x$edges))
   inEdges = nodes(object)[unlist(sapply(object@edgeL, function(x)
                x$edges))]
   numIn = table(inEdges)
   return(list(numOut = numOut, numIn = numIn))
 }

##FIXME: this is a replacement for the inEdges function -
##it needs to be tested and made to handle a list of
##nodes to find in edges - but that can easily be done
##simply by computing all and then subsetting
 inE <- function(object) {
   if(!(edgemode(object)) == "directed")
      stop("only for directed graphs")
   inEdges = nodes(object)[unlist(sapply(object@edgeL, function(x)
                x$edges))]
   numE = sapply(object@edgeL, function(x) length(x$edges))
   froms = rep(names(object@edgeL), numE)
   split(froms, inEdges)
 }

Try the graph package in your browser

Any scripts or data that you put into this service are public.

graph documentation built on Nov. 8, 2020, 6:02 p.m.