R - statnet - grp month - full month
2017.12.02 - work log - prelim - R - statnet - grp month
Related files:
network descriptives
network-level
files
R scripts:
context_text/R/db_connect.r
context_text/R/sna/functions-sna.r
context_text/R/sna/sna-load_data.r
context_text/R/sna/igraph/*
context_text/R/sna/statnet/*
statnet/sna
sna::gden()
- graph densityR scripts:
context_text/R/sna/statnet/sna-statnet-init.r
context_text/R/sna/statnet/sna-statnet-network-stats.r
context_text/R/sna/statnet/sna-qap.r
igraph
igraph::transitivity()
- vector of transitivity scores for each node in a graph, plus network-level transitivity score.
R scripts:
context_text/R/sna/statnet/sna-igraph-init.r
context_text/R/sna/statnet/sna-igraph-network-stats.r
Store important directories and file names in variables:
In [1]:
getwd()
In [2]:
# code files (in particular SNA function library, modest though it may be)
code_directory <- "/home/jonathanmorgan/work/django/research/context_analysis/R/sna"
sna_function_file_path <- paste( code_directory, "/", 'functions-sna.r', sep = "" )
# home directory
home_directory <- getwd()
home_directory <- "/home/jonathanmorgan/work/django/research/work/phd_work/methods"
# data directories
data_directory <- paste( home_directory, "/data", sep = "" )
workspace_file_name <- "statnet-grp_month.RData"
workspace_file_path <- paste( data_directory, "/", workspace_file_name )
In [3]:
# set working directory to data directory for now.
setwd( data_directory )
getwd()
In [4]:
source( sna_function_file_path )
source the file statnet/functions-statnet.r
.
In [5]:
# statnet/sna functions
# - /home/jonathanmorgan/work/django/research/context_analysis/R/sna/stanet/functions-statnet.r
statnetFunctionFilePath <- paste( code_directory, "/statnet/", 'functions-statnet.r', sep = "" )
In [6]:
source( statnetFunctionFilePath )
First, need render to render network data and upload it to your server.
Directions for rendering network data are in methods-network_analysis-create_network_data.ipynb. You want a tab-delimited matrix that includes both the network and attributes of nodes as columns, and you want it to include a header row.
Once you render your network data files, you should place them on the server.
High level data file layout:
person_id
and person_type
)Files and their location on server:
This is data from the Grand Rapids Press articles from December of 2009, coded by both humans and OpenCalais.
Files:
sourcenet_data-20171205-022551-grp_month-automated.tab
sourcenet_data-20171115-043102-grp_month-human.tab
Location in Dropbox: Dropbox/academia/MSU/program_stuff/prelim_paper/data/network_analysis/2017.11.14/network/new_coders/grp_month
Location on server: /home/jonathanmorgan/work/django/research/work/phd_work/data/network/grp_month
You must load this file's workspace, from a previous run, if one exists:
In [7]:
# assumes that you've already set working directory above to the
# working directory.
setwd( data_directory )
load( workspace_file_name )
grp_month
(gm) - automated - OpenCalaisFirst, we'll analyze the month of data coded by OpenCalais. Set up some variables to store where data is located:
grp_month
(gm) - automated - Read dataRead in the data from tab-delimited data file, then get it in right data structures for use in R SNA.
In [5]:
# initialize variables
gmAutomatedDataFolder <- paste( data_directory, "/network/grp_month", sep = "" )
gmAutomatedDataFile <- "sourcenet_data-20171205-022551-grp_month-automated.tab"
gmAutomatedDataPath <- paste( gmAutomatedDataFolder, "/", gmAutomatedDataFile, sep = "" )
In [6]:
gmAutomatedDataPath
Load the data file into memory
In [7]:
# tab-delimited:
gmAutomatedDataDF <- read.delim( gmAutomatedDataPath, header = TRUE, row.names = 1, check.names = FALSE )
In [8]:
# get count of rows...
gmAutomatedRowCount <- nrow( gmAutomatedDataDF )
paste( "grp_month automated row count = ", gmAutomatedRowCount, sep = "" )
# ...and columns
gmAutomatedColumnCount <- ncol( gmAutomatedDataDF )
paste( "grp_month automated column count = ", gmAutomatedColumnCount, sep = "" )
Get just the tie rows and columns for initializing network libraries.
In [9]:
# the below syntax returns only as many columns as there are rows, so
# omitting any trait columns that lie in columns on the right side
# of the file.
gmAutomatedNetworkDF <- gmAutomatedDataDF[ , 1 : gmAutomatedRowCount ]
#str( gmAutomatedNetworkDF )
In [10]:
# convert to a matrix
gmAutomatedNetworkMatrix <- as.matrix( gmAutomatedNetworkDF )
# str( gmAutomatedNetworkMatrix )
grp_month
(gm) - automated - initialize statnetFirst, load the statnet package, then load the automated grp_month data into statnet object and assign attributes to nodes.
Based on context_text/R/sna/statnet/sna-statnet-init.r
.
In [11]:
# make sure you've loaded the statnet library
# install.packages( "statnet" )
library( statnet )
In [12]:
# If you have a data frame of attributes (each attribute is a column, with
# attribute name the column name), you can associate those attributes
# when you create the network.
# attribute help: http://www.inside-r.org/packages/cran/network/docs/loading.attributes
# load attributes from a file:
#tab_attribute_test1 <- read.delim( "tab-test1-attribute_data.txt", header = TRUE, row.names = 1, check.names = FALSE )
# or create DataFrame by just grabbing the attribute columns
gmAutomatedNetworkAttributeDF <- gmAutomatedDataDF[ , 1168:1169 ]
# convert matrix to statnet network object instance.
gmAutomatedNetworkStatnet <- network( gmAutomatedNetworkMatrix, matrix.type = "adjacency", directed = FALSE, vertex.attr = gmAutomatedNetworkAttributeDF )
# look at information now.
gmAutomatedNetworkStatnet
# Network attributes:
# vertices = 314
# directed = FALSE
# hyper = FALSE
# loops = FALSE
# multiple = FALSE
# bipartite = FALSE
# total edges= 309
# missing edges= 0
# non-missing edges= 309
#
# Vertex attribute names:
# person_type vertex.names
#
# No edge attributes
In [13]:
# calais - include ties Greater than or equal to 0 (GE0)
gmAutomatedMeanTieWeightGE0Vector <- apply( gmAutomatedNetworkMatrix, 1, calculateListMean )
gmAutomatedDataDF$meanTieWeightGE0 <- gmAutomatedMeanTieWeightGE0Vector
# calais - include ties Greater than or equal to 1 (GE1)
gmAutomatedMeanTieWeightGE1Vector <- apply( gmAutomatedNetworkMatrix, 1, calculateListMean, minValueToIncludeIN = 1 )
gmAutomatedDataDF$meanTieWeightGE1 <- gmAutomatedMeanTieWeightGE1Vector
# automated - Max tie weight?
gmAutomatedMaxTieWeightVector <- apply( gmAutomatedNetworkMatrix, 1, calculateListMax )
gmAutomatedDataDF$maxTieWeight <- gmAutomatedMaxTieWeightVector
grp_month
(gm) - automated - Basic metrics
In [14]:
# assuming that our statnet network object is in reference test1_statnet.
# Use the degree function in the sna package to create vector of degree values
# for each node. Make sure to pass the gmode parameter to tell it that the
# graph is not directed (gmode = "graph", instead of "digraph").
# Doc: http://www.inside-r.org/packages/cran/sna/docs/degree
#degree_vector <- degree( test1_statnet, gmode = "graph" )
# If you have other libraries loaded that also implement a degree function, you
# can also call this with package name:
gmAutomatedDegreeVector <- sna::degree( gmAutomatedNetworkStatnet, gmode = "graph" )
# output the vector
gmAutomatedDegreeVector
# want more info on the degree function? You can get to it eventually through
# the following:
#help( package = "sna" )
#??sna::degree
# what is the average (mean) degree?
gmAutomatedAvgDegree <- mean( gmAutomatedDegreeVector )
paste( "average degree = ", gmAutomatedAvgDegree, sep = "" )
# subset vector to get only those that are above mean
gmAutomatedAboveMeanVector <- gmAutomatedDegreeVector[ gmAutomatedDegreeVector > gmAutomatedAvgDegree ]
# Take the degree and associate it with each node as a node attribute.
# (%v% is a shortcut for the get.vertex.attribute command)
gmAutomatedNetworkStatnet %v% "degree" <- gmAutomatedDegreeVector
# also add degree vector to original data frame
gmAutomatedDataDF$degree <- gmAutomatedDegreeVector
In [15]:
# average author degree (person types 2 and 4)
gmAutomatedAverageAuthorDegree2And4 <- calcAuthorMeanDegree( dataFrameIN = gmAutomatedDataDF, includeBothIN = TRUE )
paste( "average author degree (2 and 4) = ", gmAutomatedAverageAuthorDegree2And4, sep = "" )
# average author degree (person type 2 only)
gmAutomatedAverageAuthorDegreeOnly2 <- calcAuthorMeanDegree( dataFrameIN = gmAutomatedDataDF, includeBothIN = FALSE )
paste( "average author degree (only 2) = ", gmAutomatedAverageAuthorDegreeOnly2, sep = "" )
# average source degree (person types 3 and 4)
gmAutomatedAverageSourceDegree3And4 <- calcSourceMeanDegree( dataFrameIN = gmAutomatedDataDF, includeBothIN = TRUE )
paste( "average source degree (3 and 4) = ", gmAutomatedAverageSourceDegree3And4, sep = "" )
# average source degree (person type 3 only)
gmAutomatedAverageSourceDegreeOnly3 <- calcSourceMeanDegree( dataFrameIN = gmAutomatedDataDF, includeBothIN = FALSE )
paste( "average source degree (only 3) = ", gmAutomatedAverageSourceDegreeOnly3, sep = "" )
grp_month
(gm) - automated - More metricsNow that we have the data in statnet object, run the code in the following for more in-depth information:
context_text/R/sna/statnet/sna-statnet-network-stats.r
In [16]:
# Links:
# - manual (PDF): http://cran.r-project.org/web/packages/sna/sna.pdf
# - good notes: http://www.shizukalab.com/toolkits/sna/node-level-calculations
# Also, be advised that statnet and igraph don't really play nice together.
# If you'll be using both, best idea is to have a workspace for each.
#==============================================================================#
# statnet
#==============================================================================#
# make sure you've loaded the statnet library (includes sna)
# install.packages( "statnet" )
#library( statnet )
#==============================================================================#
# NODE level
#==============================================================================#
# what is the standard deviation of the degrees?
gmAutomatedDegreeSd <- sd( gmAutomatedDegreeVector )
paste( "degree SD = ", gmAutomatedDegreeSd, sep = "" )
# what is the variance of the degrees?
gmAutomatedDegreeVar <- var( gmAutomatedDegreeVector )
paste( "degree variance = ", gmAutomatedDegreeVar, sep = "" )
# what is the max value among the degrees?
gmAutomatedDegreeMax <- max( gmAutomatedDegreeVector )
paste( "degree max = ", gmAutomatedDegreeMax, sep = "" )
# calculate and plot degree distributions
gmAutomatedDegreeFrequenciesTable <- table( gmAutomatedDegreeVector )
paste( "degree frequencies = ", gmAutomatedDegreeFrequenciesTable, sep = "" )
gmAutomatedDegreeFrequenciesTable
# node-level undirected betweenness
gmAutomatedBetweenness <- sna::betweenness( gmAutomatedNetworkStatnet, gmode = "graph", cmode = "undirected" )
#paste( "betweenness = ", gmAutomatedBetweenness, sep = "" )
# associate with each node as a node attribute.
# (%v% is a shortcut for the get.vertex.attribute command)
gmAutomatedNetworkStatnet %v% "betweenness" <- gmAutomatedBetweenness
# also add degree vector to original data frame
gmAutomatedDataDF$betweenness <- gmAutomatedBetweenness
#==============================================================================#
# NETWORK level
#==============================================================================#
# graph-level degree centrality
gmAutomatedDegreeCentrality <- sna::centralization( gmAutomatedNetworkStatnet, sna::degree, mode = "graph" )
paste( "degree centrality = ", gmAutomatedDegreeCentrality, sep = "" )
# graph-level betweenness centrality
gmAutomatedBetweennessCentrality <- sna::centralization( gmAutomatedNetworkStatnet, sna::betweenness, mode = "graph", cmode = "undirected" )
paste( "betweenness centrality = ", gmAutomatedBetweennessCentrality, sep = "" )
# graph-level connectedness
gmAutomatedConnectedness <- sna::connectedness( gmAutomatedNetworkStatnet )
paste( "connectedness = ", gmAutomatedConnectedness, sep = "" )
# graph-level transitivity
gmAutomatedTransitivity <- sna::gtrans( gmAutomatedNetworkStatnet, mode = "graph" )
paste( "transitivity = ", gmAutomatedTransitivity, sep = "" )
# graph-level density
gmAutomatedDensity <- sna::gden( gmAutomatedNetworkStatnet, mode = "graph" )
paste( "density = ", gmAutomatedDensity, sep = "" )
grp_month
(gm) - automated - create node attribute DataFrameIf you want to just work with the traits of the nodes/vertexes, you can combine the attribute vectors into a data frame.
In [17]:
#==============================================================================#
# output attributes to data frame
#==============================================================================#
# if you want to just work with the traits of the nodes/vertexes, you can
# combine the attribute vectors into a data frame.
# first, output network object to see what attributes you have
gmAutomatedNetworkStatnet
# then, combine them into a data frame.
gmAutomatedNodeAttrDF <- data.frame( id = gmAutomatedNetworkStatnet %v% "vertex.names",
person_id = gmAutomatedNetworkStatnet %v% "person_id",
person_type = gmAutomatedNetworkStatnet %v% "person_type",
degree = gmAutomatedNetworkStatnet %v% "degree",
betweenness = gmAutomatedNetworkStatnet %v% "betweenness" )
grp_month
(gm) - humanNext, we'll analyze the month of data coded by human coders. Set up some variables to store where data is located:
grp_month
(gm) - human - Read dataRead in the data from tab-delimited data file, then get it in right data structures for use in R SNA.
In [18]:
# initialize variables
gmHumanDataFolder <- paste( data_directory, "/network/grp_month", sep = "" )
gmHumanDataFile <- "sourcenet_data-20171115-043102-grp_month-human.tab"
gmHumanDataPath <- paste( gmHumanDataFolder, "/", gmHumanDataFile, sep = "" )
In [19]:
gmHumanDataPath
Load the data file into memory
In [20]:
# tab-delimited:
gmHumanDataDF <- read.delim( gmHumanDataPath, header = TRUE, row.names = 1, check.names = FALSE )
In [21]:
# get count of rows...
gmHumanRowCount <- nrow( gmHumanDataDF )
paste( "grp_month automated row count = ", gmHumanRowCount, sep = "" )
# ...and columns
gmHumanColumnCount <- ncol( gmHumanDataDF )
paste( "grp_month automated column count = ", gmHumanColumnCount, sep = "" )
Get just the tie rows and columns for initializing network libraries.
In [22]:
# the below syntax returns only as many columns as there are rows, so
# omitting any trait columns that lie in columns on the right side
# of the file.
gmHumanNetworkDF <- gmHumanDataDF[ , 1 : gmHumanRowCount ]
#str( gmHumanNetworkDF )
In [23]:
# convert to a matrix
gmHumanNetworkMatrix <- as.matrix( gmHumanNetworkDF )
# str( gmHumanNetworkMatrix )
grp_month
(gm) - human - initialize statnetFirst, load the statnet package, then load the automated grp_month data into statnet object and assign attributes to nodes.
Based on context_text/R/sna/statnet/sna-statnet-init.r
.
In [24]:
# make sure you've loaded the statnet library
# install.packages( "statnet" )
library( statnet )
In [25]:
# If you have a data frame of attributes (each attribute is a column, with
# attribute name the column name), you can associate those attributes
# when you create the network.
# attribute help: http://www.inside-r.org/packages/cran/network/docs/loading.attributes
# load attributes from a file:
#tab_attribute_test1 <- read.delim( "tab-test1-attribute_data.txt", header = TRUE, row.names = 1, check.names = FALSE )
# or create DataFrame by just grabbing the attribute columns
#gmHumanNetworkAttributeDF <- gmHumanDataDF[ , 1169:1170 ]
gmHumanNetworkAttributeDF <- gmHumanDataDF[ , 1168:1169 ]
# convert matrix to statnet network object instance.
gmHumanNetworkStatnet <- network( gmHumanNetworkMatrix, matrix.type = "adjacency", directed = FALSE, vertex.attr = gmHumanNetworkAttributeDF )
# look at information now.
gmHumanNetworkStatnet
# Network attributes:
# vertices = 314
# directed = FALSE
# hyper = FALSE
# loops = FALSE
# multiple = FALSE
# bipartite = FALSE
# total edges= 309
# missing edges= 0
# non-missing edges= 309
#
# Vertex attribute names:
# person_type vertex.names
#
# No edge attributes
In [26]:
# human - include ties Greater than or equal to 0 (GE0)
gmHumanMeanTieWeightGE0Vector <- apply( gmHumanNetworkMatrix, 1, calculateListMean )
gmHumanDataDF$meanTieWeightGE0 <- gmHumanMeanTieWeightGE0Vector
# human - include ties Greater than or equal to 1 (GE1)
gmHumanMeanTieWeightGE1Vector <- apply( gmHumanNetworkMatrix, 1, calculateListMean, minValueToIncludeIN = 1 )
gmHumanDataDF$meanTieWeightGE1 <- gmHumanMeanTieWeightGE1Vector
# human - Max tie weight?
gmHumanMaxTieWeightVector <- apply( gmHumanNetworkMatrix, 1, calculateListMax )
gmHumanDataDF$maxTieWeight <- gmHumanMaxTieWeightVector
grp_month
(gm) - human - Basic metrics
In [27]:
# assuming that our statnet network object is in reference test1_statnet.
# Use the degree function in the sna package to create vector of degree values
# for each node. Make sure to pass the gmode parameter to tell it that the
# graph is not directed (gmode = "graph", instead of "digraph").
# Doc: http://www.inside-r.org/packages/cran/sna/docs/degree
#degree_vector <- degree( test1_statnet, gmode = "graph" )
# If you have other libraries loaded that also implement a degree function, you
# can also call this with package name:
gmHumanDegreeVector <- sna::degree( gmHumanNetworkStatnet, gmode = "graph" )
# output the vector
gmHumanDegreeVector
# want more info on the degree function? You can get to it eventually through
# the following:
#help( package = "sna" )
#??sna::degree
# what is the average (mean) degree?
gmHumanAvgDegree <- mean( gmHumanDegreeVector )
paste( "average degree = ", gmHumanAvgDegree, sep = "" )
# subset vector to get only those that are above mean
gmHumanAboveMeanVector <- gmHumanDegreeVector[ gmHumanDegreeVector > gmHumanAvgDegree ]
# Take the degree and associate it with each node as a node attribute.
# (%v% is a shortcut for the get.vertex.attribute command)
gmHumanNetworkStatnet %v% "degree" <- gmHumanDegreeVector
# also add degree vector to original data frame
gmHumanDataDF$degree <- gmHumanDegreeVector
In [28]:
# average author degree (person types 2 and 4)
gmHumanAverageAuthorDegree2And4 <- calcAuthorMeanDegree( dataFrameIN = gmHumanDataDF, includeBothIN = TRUE )
paste( "average author degree (2 and 4) = ", gmHumanAverageAuthorDegree2And4, sep = "" )
# average author degree (person type 2 only)
gmHumanAverageAuthorDegreeOnly2 <- calcAuthorMeanDegree( dataFrameIN = gmHumanDataDF, includeBothIN = FALSE )
paste( "average author degree (only 2) = ", gmHumanAverageAuthorDegreeOnly2, sep = "" )
# average source degree (person types 3 and 4)
gmHumanAverageSourceDegree3And4 <- calcSourceMeanDegree( dataFrameIN = gmHumanDataDF, includeBothIN = TRUE )
paste( "average source degree (3 and 4) = ", gmHumanAverageSourceDegree3And4, sep = "" )
# average source degree (person type 3 only)
gmHumanAverageSourceDegreeOnly3 <- calcSourceMeanDegree( dataFrameIN = gmHumanDataDF, includeBothIN = FALSE )
paste( "average source degree (only 3) = ", gmHumanAverageSourceDegreeOnly3, sep = "" )
grp_month
(gm) - human - More metricsNow that we have the data in statnet object, run the code in the following for more in-depth information:
context_text/R/sna/statnet/sna-statnet-network-stats.r
In [29]:
# Links:
# - manual (PDF): http://cran.r-project.org/web/packages/sna/sna.pdf
# - good notes: http://www.shizukalab.com/toolkits/sna/node-level-calculations
# Also, be advised that statnet and igraph don't really play nice together.
# If you'll be using both, best idea is to have a workspace for each.
#==============================================================================#
# statnet
#==============================================================================#
# make sure you've loaded the statnet library (includes sna)
# install.packages( "statnet" )
#library( statnet )
#==============================================================================#
# NODE level
#==============================================================================#
# what is the standard deviation of the degrees?
gmHumanDegreeSd <- sd( gmHumanDegreeVector )
paste( "degree SD = ", gmHumanDegreeSd, sep = "" )
# what is the variance of the degrees?
gmHumanDegreeVar <- var( gmHumanDegreeVector )
paste( "degree variance = ", gmHumanDegreeVar, sep = "" )
# what is the max value among the degrees?
gmHumanDegreeMax <- max( gmHumanDegreeVector )
paste( "degree max = ", gmHumanDegreeMax, sep = "" )
# calculate and plot degree distributions
gmHumanDegreeFrequenciesTable <- table( gmHumanDegreeVector )
paste( "degree frequencies = ", gmHumanDegreeFrequenciesTable, sep = "" )
gmHumanDegreeFrequenciesTable
# node-level undirected betweenness
gmHumanBetweenness <- sna::betweenness( gmHumanNetworkStatnet, gmode = "graph", cmode = "undirected" )
#paste( "betweenness = ", gmHumanBetweenness, sep = "" )
# associate with each node as a node attribute.
# (%v% is a shortcut for the get.vertex.attribute command)
gmHumanNetworkStatnet %v% "betweenness" <- gmHumanBetweenness
# also add degree vector to original data frame
gmHumanDataDF$betweenness <- gmHumanBetweenness
#==============================================================================#
# NETWORK level
#==============================================================================#
# graph-level degree centrality
gmHumanDegreeCentrality <- sna::centralization( gmHumanNetworkStatnet, sna::degree, mode = "graph" )
paste( "degree centrality = ", gmHumanDegreeCentrality, sep = "" )
# graph-level betweenness centrality
gmHumanBetweennessCentrality <- sna::centralization( gmHumanNetworkStatnet, sna::betweenness, mode = "graph", cmode = "undirected" )
paste( "betweenness centrality = ", gmHumanBetweennessCentrality, sep = "" )
# graph-level connectedness
gmHumanConnectedness <- sna::connectedness( gmHumanNetworkStatnet )
paste( "connectedness = ", gmHumanConnectedness, sep = "" )
# graph-level transitivity
gmHumanTransitivity <- sna::gtrans( gmHumanNetworkStatnet, mode = "graph" )
paste( "transitivity = ", gmHumanTransitivity, sep = "" )
# graph-level density
gmHumanDensity <- sna::gden( gmHumanNetworkStatnet, mode = "graph" )
paste( "density = ", gmHumanDensity, sep = "" )
grp_month
(gm) - human - create node attribute DataFrameIf you want to just work with the traits of the nodes/vertexes, you can combine the attribute vectors into a data frame.
In [30]:
#==============================================================================#
# output attributes to data frame
#==============================================================================#
# if you want to just work with the traits of the nodes/vertexes, you can
# combine the attribute vectors into a data frame.
# first, output network object to see what attributes you have
gmHumanNetworkStatnet
# then, combine them into a data frame.
gmHumanNodeAttrDF <- data.frame( id = gmHumanNetworkStatnet %v% "vertex.names",
person_id = gmHumanNetworkStatnet %v% "person_id",
person_type = gmHumanNetworkStatnet %v% "person_type",
degree = gmHumanNetworkStatnet %v% "degree",
betweenness = gmHumanNetworkStatnet %v% "betweenness" )
grp_month
QAP graph correlation between automated and ground truthNow, compare the automated and human-coded networks themselves using graph correlation in QAP.
Based on: context_text/R/sna/statnet/sna-qap.r
In [8]:
outputPrefix <- "grp_month a2b"
In [9]:
grpMontha2bOutput <- compareMatricesQAP( gmHumanNetworkMatrix, gmAutomatedNetworkMatrix, outputPrefix )
In [ ]:
# also output plots of distributions of QAP values?
displayCompareMatricesQAPOutput( grpMontha2bOutput, outputPrefix, TRUE )
Save all the information in the current image, in case we need/want it later.
In [10]:
# help( save.image )
message( paste( "Output workspace to: ", workspace_file_name, sep = "" ) )
save.image( file = workspace_file_name )
DONE:
human data for grp_month has one fewer vertex (1167) than automated (1168). The missing person is row 355, user ID 781 (source_3), who is in automated, not in human. QAP needs same-size matrices.