suppressWarnings({library(tidyverse)
library(tmap)
library(ggplot2)
library(units)
library(sf)
library(leaflet)
library(tidycensus)
library(leafsync)
library(dbscan)
library(sfnetworks)
library(tigris)
library(tidygraph)
library(plotly)
library(osmdata)
library(here)
})
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.4.0 ✔ purrr 0.3.5
## ✔ tibble 3.1.8 ✔ dplyr 1.0.10
## ✔ tidyr 1.2.1 ✔ stringr 1.5.0
## ✔ readr 2.1.3 ✔ forcats 0.5.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## udunits database from C:/Users/Mary Jane Leach/AppData/Local/R/win-library/4.2/units/share/udunits/udunits2.xml
##
## Linking to GEOS 3.9.3, GDAL 3.5.2, PROJ 8.2.1; sf_use_s2() is TRUE
##
##
## Attaching package: 'dbscan'
##
##
## The following object is masked from 'package:stats':
##
## as.dendrogram
##
##
## To enable caching of data, set `options(tigris_use_cache = TRUE)`
## in your R script or .Rprofile.
##
##
## Attaching package: 'tidygraph'
##
##
## The following object is masked from 'package:stats':
##
## filter
##
##
##
## Attaching package: 'plotly'
##
##
## The following object is masked from 'package:ggplot2':
##
## last_plot
##
##
## The following object is masked from 'package:stats':
##
## filter
##
##
## The following object is masked from 'package:graphics':
##
## layout
##
##
## Data (c) OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright
##
## here() starts at C:/Users/Mary Jane Leach/OneDrive - Georgia Institute of Technology/Desktop/VIS_FIN/VIS_FIN
bb <- getbb('Atlanta, GA')
# Converting bb into an sf object
bb_sf <- bb %>% t %>% data.frame() %>%
st_as_sf(coords = c("x", "y"), crs = 4326) %>%
st_bbox() %>%
st_as_sfc()
## Plot--
tmap_mode('view')
## tmap mode set to interactive viewing
osm_road <- opq(bbox = bb) %>%
add_osm_feature(key = 'highway',
value = c("motorway", "trunk", "primary",
"secondary", "tertiary", "unclassified",
"residential")) %>%
osmdata_sf() %>%
osm_poly2line()
names(osm_road)
## [1] "bbox" "overpass_call" "meta"
## [4] "osm_points" "osm_lines" "osm_polygons"
## [7] "osm_multilines" "osm_multipolygons"
round( prop.table(table(osm_road$osm_lines$highway)) * 100, 1 )
##
## motorway primary residential secondary tertiary trunk
## 3.6 3.7 68.5 11.6 8.4 2.3
## unclassified
## 1.8
p1 <- c(33.742095, -84.368660)
p2 <- c(33.786261, -84.355831)
my_bb <- matrix(c(p1[2], p1[1],
p2[2], p2[1]), ncol = 2)
colnames(my_bb) <- c("min", "max")
rownames(my_bb) <- c("x", "y")
# Custom BB to sf
my_bb_sf <- my_bb %>% t %>% data.frame() %>%
st_as_sf(coords = c("x", "y"), crs = 4326) %>%
st_bbox() %>%
st_as_sfc()
# Extract a smaller network for exercise purpose
osm_small <- osm_road$osm_lines[my_bb_sf,]
## Plot--
tmap_mode('view')
## tmap mode set to interactive viewing
tm_shape(bb_sf) + tm_borders(col = "black") + # Black = larger bbox
tm_shape(my_bb_sf) + tm_borders(col = "red") + # Red = smaller bbox
tm_shape(osm_small) + tm_lines(col = "black") # Black line = small network
net <- sfnetworks::as_sfnetwork(osm_small, directed = FALSE)
print(net)
## # A sfnetwork with 471 nodes and 349 edges
## #
## # CRS: EPSG:4326
## #
## # An undirected multigraph with 128 components with spatially explicit edges
## #
## # Node Data: 471 × 1 (active)
## # Geometry type: POINT
## # Dimension: XY
## # Bounding box: xmin: -84.37778 ymin: 33.73043 xmax: -84.34923 ymax: 33.7971
## geometry
## <POINT [°]>
## 1 (-84.37242 33.74325)
## 2 (-84.36723 33.74325)
## 3 (-84.35823 33.7439)
## 4 (-84.35822 33.74312)
## 5 (-84.36012 33.76413)
## 6 (-84.35919 33.76446)
## # … with 465 more rows
## #
## # Edge Data: 349 × 228
## # Geometry type: LINESTRING
## # Dimension: XY
## # Bounding box: xmin: -84.37778 ymin: 33.73043 xmax: -84.34923 ymax: 33.7971
## from to osm_id name abando… abando… abando… access access… addr.c…
## <int> <int> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 1 2 91862… Ralp… <NA> <NA> <NA> <NA> <NA> <NA>
## 2 3 4 92348… Bill… <NA> <NA> <NA> <NA> <NA> <NA>
## 3 5 6 92363… <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## # … with 346 more rows, and 218 more variables: addr.county <chr>,
## # addr.housenumber <chr>, addr.postcode <chr>, addr.state <chr>,
## # addr.street <chr>, alt_name <chr>, alt_name_1 <chr>, attribution <chr>,
## # bicycle <chr>, bridge <chr>, bridge.name <chr>, bus <chr>, bus.lanes <chr>,
## # center_turn_lane <chr>, centre_turn_lane <chr>, change <chr>,
## # change.backward <chr>, change.forward <chr>, change.lanes <chr>,
## # change.lanes.backward <chr>, change.lanes.forward <chr>, check_date <chr>,
## # check_date.surface <chr>, class.bicycle <chr>, construction <chr>,
## # covered <chr>, created_by <chr>, cycleway <chr>, cycleway.both <chr>,
## # cycleway.both.buffer <chr>, cycleway.both.lane <chr>,
## # cycleway.est_width <chr>, cycleway.left <chr>, cycleway.left.buffer <chr>,
## # cycleway.right <chr>, cycleway.right.buffer <chr>,
## # cycleway.right.segregated <chr>, description <chr>, destination <chr>,
## # destination.backward <chr>, destination.lanes <chr>,
## # destination.lanes.forward <chr>, destination.ref <chr>,
## # destination.ref.lanes <chr>, destination.ref.to <chr>,
## # destination.ref.to.lanes <chr>, embedded_rails <chr>, expressway <chr>,
## # fee <chr>, fixme <chr>, FIXME <chr>, foot <chr>, ford <chr>,
## # gatech.PRKG_NUM <chr>, goods <chr>, HFCS <chr>, hgv <chr>, hgv.lanes <chr>,
## # hgv.minaxles <chr>, highway <chr>, highway_1 <chr>, horse <chr>, hov <chr>,
## # hov.lanes <chr>, incline <chr>, junction <chr>, junction.ref <chr>,
## # LandPro08.DE3 <chr>, LandPro08.DE5 <chr>, lane_markings <chr>, lanes <chr>,
## # lanes.backward <chr>, lanes.both_ways <chr>, lanes.forward <chr>,
## # layer <chr>, level <chr>, lit <chr>, loc_name <chr>, maxheight <chr>,
## # maxlength <chr>, maxspeed <chr>, maxspeed.type <chr>,
## # maxspeed.variable <chr>, maxweight <chr>, maxweight.signed <chr>,
## # minspeed <chr>, motor_vehicle <chr>, motor_vehicle.conditional <chr>,
## # motor_vehicle.lanes <chr>, motorcycle <chr>, motorcycle.lanes <chr>,
## # name.etymology.wikidata <chr>, name_ <chr>, name_1 <chr>, name_2 <chr>,
## # name_3 <chr>, NHS <chr>, noexit <chr>, noname <chr>, note <chr>, …
leaflet() %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
setView( -84.362080, 33.768204, zoom = 13) %>% # zooming in to show more details
addPolylines(data = net %>% st_as_sf('edges'), col = "gray", weight = 3, opacity = 0.9, popup = net %>% st_as_sf('edges') %>% pull(osm_id)) %>%
addCircles(data = net %>% st_as_sf('nodes'), fillColor = 'yellow',
stroke = F, radius = 20, fillOpacity = 0.7)
simple_net <- net %>%
activate("edges") %>%
filter(!edge_is_multiple()) %>%
filter(!edge_is_loop())
# Because the difference is not really discernible, we just print out the differences in the edge count.
message(str_c("Before simplification, there were ", net %>% st_as_sf("edges") %>% nrow(), " edges. \n",
"After simplification, there are ", simple_net %>% st_as_sf("edges") %>% nrow(), " edges."))
## Before simplification, there were 349 edges.
## After simplification, there are 347 edges.
subdiv_net <- convert(simple_net, sfnetworks::to_spatial_subdivision)
## Warning: to_spatial_subdivision assumes attributes are constant over geometries
subdiv_net <- subdiv_net %>%
activate("nodes") %>%
mutate(custom_id = seq(1, subdiv_net %>% st_as_sf("nodes") %>% nrow()),
is.new = case_when(is.na(.tidygraph_node_index) ~ "new nodes",
TRUE ~ "existing nodes"),
is.new = factor(is.new))
## Plot--
subdiv_pal <- colorFactor(palette = c("yellow", "red"), domain = subdiv_net %>% st_as_sf("nodes") %>% pull(is.new))
subdiv_map <- leaflet() %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
setView(-84.362080, 33.768204, zoom = 13) %>% # zooming in to show more details
addPolylines(data = subdiv_net %>% st_as_sf('edges'), col = "grey", weight = 3, opacity = 0.9) %>%
addCircles(data = subdiv_net %>% st_as_sf('nodes'), fillColor = ~subdiv_pal(is.new), stroke = F, radius = 20, fillOpacity = 0.7) %>%
addLegend(position = "bottomright", pal = subdiv_pal, values = subdiv_net %>% st_as_sf("nodes") %>% pull(is.new))
subdiv_map
# Using spatial morpher
smoothed_net <- convert(subdiv_net, sfnetworks::to_spatial_smooth)
# -------------------------------------------
# Below is done for visualization purpose
# Extract removed points for mapping purposes
removed <- setdiff(subdiv_net %>% st_as_sf('nodes') %>% pull(custom_id),
smoothed_net %>% st_as_sf('nodes') %>% pull(custom_id))
smooth_map <- leaflet() %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
setView(-84.362080, 33.768204, zoom = 13) %>% # zooming in to show more details
addPolylines(data = smoothed_net %>% st_as_sf('edges'), col = "grey", weight = 3, opacity = 0.9, popup = smoothed_net %>% st_as_sf('edges') %>% rownames()) %>%
addCircles(data = smoothed_net %>% st_as_sf('nodes'), fillColor = 'yellow', stroke = F, radius = 20, fillOpacity = 0.7, group = "kept") %>%
addCircles(data = subdiv_net %>% st_as_sf("nodes") %>% filter(custom_id %in% removed),
fillColor = "red", stroke = F, radius = 15, fillOpacity = 0.8, group = "removed") %>%
addControl(html = htmltools::HTML("<b>Red points denote removed nodes</b>"), position = "bottomright") %>%
addLayersControl(overlayGroups = c("kept", "removed"))
smooth_map
network_char <- smoothed_net %>%
activate("edges") %>%
mutate(weight = edge_length()) %>%
mutate(edge_bc = centrality_edge_betweenness(weights = weight, directed = F)) %>%
activate("nodes") %>%
mutate(node_bc = centrality_betweenness(weights = weight, directed = F))
## Warning in betweenness(graph = graph, v = V(graph), directed = directed, :
## 'nobigint' is deprecated since igraph 1.3 and will be removed in igraph 1.4
bet_pal_edge <- colorNumeric(palette = "Reds", domain = network_char %>% activate("edges") %>% pull(edge_bc), n = 6)
# Node betweenness
bet_pal_node <- colorNumeric(palette = "Reds", domain = network_char %>% activate("nodes") %>% pull(node_bc), n = 6)
# Map
leaflet() %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
setView(-84.362080, 33.768204, zoom = 13) %>% # zooming in to show more details
addPolylines(data = network_char %>% st_as_sf("edges"),
color = ~bet_pal_edge(network_char %>% st_as_sf('edges') %>% pull(edge_bc)), weight = 3, opacity = 0.7) %>%
addCircles(data = network_char %>% st_as_sf("nodes"),
fillColor = ~bet_pal_node(network_char %>% st_as_sf('nodes') %>% pull(node_bc)), stroke = F, fillOpacity = 0.7,
radius = network_char %>% st_as_sf("nodes") %>% with(.$node_bc/(max(.$node_bc)/100))) # denominator is selected to make the max value roughly equal to 100
smoothed_net <- smoothed_net %>% st_transform(4326)
# Start point
start_p <- st_point(c(-84.36849645359781,33.78203428355537)) %>% st_sfc(crs = 4326) # Atlanta Beltline Eastside Trail 10th Street Entrance
# End point
target_p1 <- st_point(c(-84.36048845553397, 33.76470042566388)) %>% st_sfc(crs = 4326) # Old Fourth Ward Skate Park
target_p2 <- st_point(c(-84.36337643520744, 33.75209882220849)) %>% st_sfc(crs = 4326) # 97 Estoria
# Get the shortest path
paths = st_network_paths(smoothed_net, from = start_p, to = c(target_p1, target_p2), type = "shortest")
# Extract shortest path
paths_sf1 <- smoothed_net %>%
activate("nodes") %>%
slice(paths$node_paths[[1]])
paths_sf2 <- smoothed_net %>%
activate("nodes") %>%
slice(paths$node_paths[[2]])
# Visualize
leaflet() %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
setView(-84.362080, 33.768204, zoom = 13) %>% # zooming in to show more details
# Base network in grey
addPolylines(data = smoothed_net %>% st_as_sf("edges"), color = 'grey', weight = 2, opacity = 0.7) %>%
# Chosen edges
addPolylines(data = paths_sf1 %>% st_as_sf("edges"), color = "red", weight = 10, opacity = 0.5) %>%
# Chosen nodes
addCircles(data = paths_sf1 %>% st_as_sf("nodes"), stroke = F, fillColor = "red", fillOpacity = 0.8, radius = 50) %>%
# Chosen edges
addPolylines(data = paths_sf2 %>% st_as_sf("edges"), color = "green", weight = 4, opacity = 0.5) %>%
# Chosen nodes
addCircles(data = paths_sf2 %>% st_as_sf("nodes"), stroke = F, fillColor = "green", fillOpacity = 0.8, radius = 50)
intersections <- smoothed_net %>%
st_transform(crs = 4326) %>%
activate("nodes") %>%
mutate(degree = centrality_degree(weights = NULL)) %>%
filter(degree > 1)
NODES <-leaflet() %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
setView (-84.362080, 33.768204, zoom = 13) %>% # zooming in to show more details
addPolylines(data = smoothed_net %>% st_as_sf('edges'), col = "grey", weight = 3, opacity = 0.9) %>%
addCircles(data = intersections %>% st_as_sf('nodes'), fillColor = 'yellow', stroke = F, radius = 20, fillOpacity = 0.7)
tidycensus::census_api_key(Sys.getenv("censuscle"))
## To install your API key for use in future sessions, run this function with `install = TRUE`.
tract <- suppressMessages(
get_acs(geography = "tract", # or "block group", "county", "state" etc.
state = "GA",
county = c("Fulton", "Dekalb"),
variables = c(hhincE = 'B19019_001',
tot_hh = "B19001_001E",
r_totE = "B02001_001",
race.white = "B02001_002",
race.black = 'B02001_003',
TOT_DISABL = 'B18135_001E',
AGE_DIS19TO64 = 'B18135_014E',
own_novhcE = "B08137_002E",
rent_novhcE = "B08137_003E"),
year = 2020,
survey = "acs5", # American Community Survey 5-year estimate
geometry = TRUE, # returns sf objects
output = "wide") # wide vs. long
)
##
|
| | 0%
|
| | 1%
|
|= | 1%
|
|== | 2%
|
|== | 3%
|
|=== | 4%
|
|==== | 5%
|
|==== | 6%
|
|===== | 7%
|
|====== | 8%
|
|====== | 9%
|
|======= | 10%
|
|======== | 11%
|
|======== | 12%
|
|========= | 13%
|
|========== | 14%
|
|========== | 15%
|
|=========== | 15%
|
|=========== | 16%
|
|============ | 17%
|
|============ | 18%
|
|============= | 18%
|
|============= | 19%
|
|============== | 19%
|
|============== | 20%
|
|=============== | 21%
|
|=============== | 22%
|
|================ | 22%
|
|================ | 23%
|
|================= | 24%
|
|================== | 25%
|
|================== | 26%
|
|=================== | 27%
|
|==================== | 28%
|
|==================== | 29%
|
|===================== | 30%
|
|====================== | 31%
|
|====================== | 32%
|
|======================= | 32%
|
|======================= | 33%
|
|======================= | 34%
|
|======================== | 34%
|
|======================== | 35%
|
|========================= | 35%
|
|========================= | 36%
|
|========================== | 37%
|
|=========================== | 38%
|
|=========================== | 39%
|
|============================ | 40%
|
|============================= | 41%
|
|============================= | 42%
|
|============================== | 42%
|
|============================== | 43%
|
|=============================== | 44%
|
|=============================== | 45%
|
|================================ | 45%
|
|================================ | 46%
|
|================================= | 47%
|
|================================= | 48%
|
|================================== | 48%
|
|================================== | 49%
|
|=================================== | 50%
|
|=================================== | 51%
|
|==================================== | 51%
|
|==================================== | 52%
|
|===================================== | 52%
|
|===================================== | 53%
|
|====================================== | 54%
|
|======================================= | 55%
|
|======================================= | 56%
|
|======================================== | 57%
|
|========================================= | 58%
|
|========================================= | 59%
|
|========================================== | 59%
|
|========================================== | 60%
|
|=========================================== | 61%
|
|============================================ | 62%
|
|============================================ | 63%
|
|============================================= | 64%
|
|============================================= | 65%
|
|============================================== | 65%
|
|============================================== | 66%
|
|=============================================== | 67%
|
|=============================================== | 68%
|
|================================================ | 68%
|
|================================================ | 69%
|
|================================================= | 69%
|
|================================================= | 70%
|
|================================================== | 71%
|
|=================================================== | 72%
|
|=================================================== | 73%
|
|==================================================== | 74%
|
|===================================================== | 75%
|
|===================================================== | 76%
|
|====================================================== | 76%
|
|====================================================== | 77%
|
|======================================================= | 78%
|
|======================================================= | 79%
|
|======================================================== | 80%
|
|======================================================== | 81%
|
|========================================================= | 81%
|
|========================================================= | 82%
|
|========================================================== | 83%
|
|========================================================== | 84%
|
|=========================================================== | 84%
|
|=========================================================== | 85%
|
|============================================================ | 86%
|
|============================================================= | 87%
|
|============================================================= | 88%
|
|============================================================== | 88%
|
|============================================================== | 89%
|
|=============================================================== | 90%
|
|=============================================================== | 91%
|
|================================================================ | 91%
|
|================================================================ | 92%
|
|================================================================= | 92%
|
|================================================================= | 93%
|
|================================================================== | 94%
|
|================================================================== | 95%
|
|=================================================================== | 95%
|
|=================================================================== | 96%
|
|==================================================================== | 97%
|
|===================================================================== | 98%
|
|===================================================================== | 99%
|
|======================================================================| 100%
message(sprintf("nrow: %s, ncol: %s", nrow(tract), ncol(tract)))
## nrow: 530, ncol: 21
tract %>% head() %>% knitr::kable()
| 13089021908 |
Census Tract 219.08, DeKalb County, Georgia |
43191 |
10870 |
1994 |
297 |
4829 |
885 |
323 |
169 |
3368 |
812 |
4822 |
886 |
541 |
312 |
815 |
258 |
1606 |
514 |
MULTIPOLYGON (((-84.20619 3… |
| 13089020400 |
Census Tract 204, DeKalb County, Georgia |
123711 |
29481 |
1219 |
151 |
2743 |
408 |
2435 |
380 |
48 |
54 |
2743 |
408 |
29 |
34 |
1243 |
226 |
448 |
139 |
MULTIPOLYGON (((-84.34921 3… |
| 13089023410 |
Census Tract 234.10, DeKalb County, Georgia |
41065 |
10579 |
1564 |
169 |
3903 |
508 |
131 |
144 |
3562 |
541 |
3903 |
508 |
369 |
152 |
520 |
166 |
1510 |
290 |
MULTIPOLYGON (((-84.29784 3… |
| 13121010601 |
Census Tract 106.01, Fulton County, Georgia |
55479 |
11675 |
1712 |
457 |
3673 |
960 |
1057 |
545 |
2514 |
788 |
3673 |
960 |
378 |
245 |
703 |
192 |
996 |
418 |
MULTIPOLYGON (((-84.46957 3… |
| 13121008302 |
Census Tract 83.02, Fulton County, Georgia |
28214 |
6536 |
638 |
106 |
1891 |
444 |
33 |
21 |
1836 |
441 |
1686 |
437 |
166 |
85 |
246 |
90 |
235 |
134 |
MULTIPOLYGON (((-84.46315 3… |
| 13121004200 |
Census Tract 42, Fulton County, Georgia |
23906 |
4494 |
1470 |
235 |
2675 |
577 |
252 |
146 |
2423 |
606 |
2675 |
577 |
568 |
524 |
371 |
117 |
700 |
293 |
MULTIPOLYGON (((-84.42334 3… |
acs2020c <- tract %>%
select(GEOID,
hhinc = hhincEE,
r_tot = r_totEE,
r_wh = race.whiteE,
r_bl = race.blackE,
tot_hh = tot_hh,
own_novhc = own_novhcE,
rent_novhc = rent_novhcE,
tot_disbl = TOT_DISABL,
disbl_age = AGE_DIS19TO64) %>%
mutate(pct_wh = r_wh / r_tot,
pct_bl = r_bl / r_tot,
pct_novhc = own_novhc + rent_novhc/tot_hh,
pct_disbl = disbl_age / tot_disbl) %>%
mutate(area1 = unclass(st_area(.))) %>%
st_transform(26967) %>%
mutate(area2 = unclass(st_area(.))) %>%
st_transform(crs = 4326) %>%
mutate(ln_pop_den = log((r_tot / (area1/1000^2)) + 1)) %>%
filter(!is.na(hhinc),!is.na(tot_hh),!is.na(r_tot), !is.na(own_novhc),!is.na(tot_disbl),!is.na(disbl_age),!is.na(own_novhc),!is.na( own_novhc))
census_centrality <- acs2020c %>%
st_join(network_char %>% st_as_sf("nodes") %>% st_transform(crs = 4326)) %>% # When I calculated centrality measures, the CRS was 26967, so reverting it back to 4326
group_by(GEOID) %>%
summarise(n = n(),
hhinc = mean(hhinc, na.rm = T),
pct_wh = mean(pct_wh, na.rm = T),
pct_bl = mean(pct_bl, na.rm = T),
pct_novhc = mean(pct_novhc, na.rm = T),
pct_disbl = mean(pct_disbl, na.rm = T),
node_bc = mean(node_bc, na.rm = T))
tm_shape(acs2020c) + tm_polygons(col = "blue", alpha = 0.5) +
tm_shape(census_centrality) + tm_polygons(col = "node_bc", style = "quantile") +
tm_shape(my_bb_sf) + tm_borders()
census_centrality_plot <- census_centrality %>%
mutate(hhinc = log(hhinc),
pct_novhc = log(pct_novhc + 0.02)) %>%
pivot_longer(cols = c('hhinc', 'pct_wh', 'pct_bl', 'pct_novhc', 'pct_disbl' ), names_to = "variable", values_to = "value") %>%
mutate(variable = factor(variable, labels = c("Household Income (logged)","% White", "% Black", "% No HH with no cars (logged)", "% Disability")))
centrality_plot <- census_centrality_plot %>%
ggplot() +
geom_point(aes(x = node_bc, y = value), alpha = 0.2) +
facet_wrap(~variable, scales = "free_y") +
labs(x = "Centrality", title = "Centrality VS. Socio-demographics") +
theme_bw()
centrality_plot + ggpubr::stat_cor(aes(x = node_bc, y = value))
## Warning: Removed 2500 rows containing non-finite values (`stat_cor()`).
## Warning: Removed 2500 rows containing missing values (`geom_point()`).

ggplotly(centrality_plot)