In this post we will explore how to utilize systems thinking tools in R and network flows with the goal of increasing understanding of a system.
In this post we will explore how to effectively draw out Causal Loop Diagrams
after a system has been designed. The plan is to:
We will be leveraging the Systems Thinker post to obtain a thought out system, and we will add a couple Goal
nodes and flow Positive
or Negative
directions to them. Once we flow through some positive and negative energy we will see how the system responds to achieving it’s goal. This is not system dynamics. The purpose of this post is to loosely extend systems thinking and systems causal loop diagramming with network flows and utility theory to see how a goal is accomplished within a system’s structure. The end-state for this will be a clear understanding of which system components are 1) primary drivers to the goal, 2) enablers to the goal, 3) edifying the system from it’s goals (in the given structure), 4) deterring the system from it’s goals (in the given structure).
We will be using a very simple system for this post. Training causing activities, causing threat perception from managers, limiting those activities. We will extend this by adding a simple goal Employee Skill Development.
#library(remotes)
#remotes::install_github("jarrod-dalton/causalloop")
library("causalloop")
L <- CLD(from=c("TQM Training",
"TQM Activities",
"TQM Activities",
"Perceived Threat",
"Resistence by Middle Managers",
"TQM Activities"),
to=c("TQM Activities",
"TQM Training",
"Perceived Threat",
"Resistence by Middle Managers",
"TQM Activities",
"Goal: Employee Skill Development"),
polarity = c(1,
1,
1,
1,
-1,
1
)
)
# L$edges
plot(L)
Clearly we can see that increasing training will increase activities and increase the goal. But a side effect occurs with a perceived threat and action by middle managers. Let’s see if we can better identify this through a network flow algorithm.
If we simulate 1000 units flowing through the system, how does it balance with the negative feedback loop from middle managers? How does it affect the goal of the system?
library(ggplot2)
simulate_flow <- function(L, n = 1000, normalize = TRUE, plot = TRUE){
L$nodes$score = 1
for(sim in 1:n){
for(i in 1:nrow(L$edges)){
# print(i)
edge <- L$edges[i, ]
# cat("Adding ", edge$polarity, " from ", edge$from, " to ",edge$to, ".\n")
tgt_idx = which(L$nodes$node == edge$to)
src_idx = which(L$nodes$node == edge$from)
L$nodes$score[tgt_idx] = L$nodes$score[tgt_idx] +
L$nodes$score[src_idx] * edge$polarity
}
}
if(normalize){
# L$nodes$score = L$nodes$score / n
L$nodes$score = L$nodes$score / sum(L$nodes$score)
}
if(plot){
plt <- ggplot(L$nodes) +
geom_col(aes(x=reorder(node, -score),y=score, fill=score))+
scale_fill_gradient(low = "red", high = "green") +
coord_flip() +
theme_bw() +
ggtitle("Systemic Behavior (Desire) Polarity Chart",
subtitle = "Red = Desire to Decay | Green = Desire to Grow") +
labs(fill = "Score", x="Score", y = "Node")
}
else{
plt <- ggplot()
}
list(nodes = L$nodes,
plt = plt)
}
nodes <- simulate_flow(L, n = 1000, normalize = TRUE, plot=TRUE)
nodes$nodes
# A tibble: 5 × 3
node group score
<chr> <chr> <dbl>
1 Goal: Employee Skill Development <default> 0.0834
2 Perceived Threat <default> 0.353
3 Resistence by Middle Managers <default> 0.417
4 TQM Activities <default> -0.206
5 TQM Training <default> 0.353
Okay, let’s take a step back. What did we just do? With the following formula:
balance(nodej)=N∑n=1∑a=(i,j)∈Nbalance(nodej)+flow(i,j)∗polarity(i,j)∀j∈Jnodesj
is fixed, so the inner sum will only trigger for this specific node, then for all nodes.
This gives us a way to see how the flows are going to naturally navigate through the system. So once we accrue a flow balance for each node, we should normalize so all are compared equally.
balance(nodej)=balance(nodej)∑i=1balance(nodei)forallj∈Jnodes
nodes$plt
A few remarks.
This system isn’t horrible, because the goal will eventually be attained with more effort; but it certainly isn’t optimal.
Let’s consider some more realistic dynamics and see how this method weathers.
library("causalloop")
L <- CLD(from=c("TQM Training",
"TQM Activities",
"TQM Activities",
"Perceived Threat",
"Resistence by Middle Managers",
"TQM Activities",
"TQM Activities",
"Labor Expended on Activities"),
to=c("TQM Activities",
"TQM Training",
"Perceived Threat",
"Resistence by Middle Managers",
"TQM Activities",
"Goal: Employee Skill Development",
"Labor Expended on Activities",
"TQM Training"),
polarity = c(1,
1,
1,
1,
-1,
1,
1,
-1
)
)
# L$edges
plot(L)
Notice we now have several feedback loops. Two balancing, one reinforcing.
[[1]]
[[1]]$loopIx
[1] 1
[[1]]$label
[1] ""
[[1]]$type
[1] "Balancing"
[[1]]$loop
# A tibble: 3 × 3
from to polarity
<chr> <chr> <dbl>
1 TQM Training TQM Activities 1
2 TQM Activities Labor Expended on Activities 1
3 Labor Expended on Activities TQM Training -1
[[2]]
[[2]]$loopIx
[1] 2
[[2]]$label
[1] ""
[[2]]$type
[1] "Reinforcing"
[[2]]$loop
# A tibble: 2 × 3
from to polarity
<chr> <chr> <dbl>
1 TQM Training TQM Activities 1
2 TQM Activities TQM Training 1
[[3]]
[[3]]$loopIx
[1] 3
[[3]]$label
[1] ""
[[3]]$type
[1] "Balancing"
[[3]]$loop
# A tibble: 3 × 3
from to polarity
<chr> <chr> <dbl>
1 TQM Activities Perceived Threat 1
2 Perceived Threat Resistence by Middle Managers 1
3 Resistence by Middle Managers TQM Activities -1
attr(,"class")
[1] "feedbackLoops"
nodes <- simulate_flow(L, n = 600, normalize = TRUE, plot=TRUE)
nodes$nodes
# A tibble: 6 × 3
node group score
<chr> <chr> <dbl>
1 Goal: Employee Skill Development <default> -0.604
2 Labor Expended on Activities <default> -0.604
3 Perceived Threat <default> 0.489
4 Resistence by Middle Managers <default> 0.731
5 TQM Activities <default> -0.404
6 TQM Training <default> 1.39
Notice by adding the realistic consideration of labor expended on activities (plus the psychological affects of middle management resisting) it will balance the activities. Since the activities are already being balanced by middle management, this further balances the training themselves, because like all things, the system is connected.
nodes$plt
This system is toxic. The goal is never attained, activities go down, but there is an interesting desire within the system to decrease labor and increase training. However, through resistance the main efforts of the system are not accomplished.
What if the goal itself reinforced the training (after all, good work does pay off with rewards)? what if the resistance from managers started to increase labor on the training themselves (after a while, people get tired)?
library("causalloop")
L <- CLD(from=c("TQM Training",
"TQM Activities",
"TQM Activities",
"Perceived Threat",
"Resistence by Middle Managers",
"TQM Activities",
"TQM Activities",
"Labor Expended on Activities",
"Goal: Employee Skill Development",
"Resistence by Middle Managers"),
to=c("TQM Activities",
"TQM Training",
"Perceived Threat",
"Resistence by Middle Managers",
"TQM Activities",
"Goal: Employee Skill Development",
"Labor Expended on Activities",
"TQM Training",
"TQM Training",
"Labor Expended on Activities"),
polarity = c(1,
1,
1,
1,
-1,
1,
1,
-1,
1,
1
)
)
# L$edges
plot(L)
Now we have a reward signal from the goal back into training. Let’s see how this changes things.
nodes <- simulate_flow(L, n = 500, normalize = TRUE, plot=TRUE)
nodes$plt
Again this system is not constructive. The goal will diminish over time. The desire to decrease activities and increase training is outweighed by threats, labor, and resistance.
This is Systems Thinking and provides a framework to redesign our systems w/o any need for Systems Dynamics.
[1] https://thesystemsthinker.com/causal-loop-construction-the-basics/
[2] https://rdrr.io/github/jarrod-dalton/causalloop/man/plot.CLD.html