Introduced by Heileman, Curricular Analytics (CA) introduces a
framework that allows one to leverage complex network analysis when
investigating curricula structure, relating the ease with which a
student progresses through a curriculum to various metrics [1]. The package
**CurricularAnalytics** provides an implementation of the
CA framework for use in R. Through it one may construct, visualize, and
inspect the properties of curriculum graphs. The metrics currently
implemented are:

- Blocking Factor
- Delay Factor
- Centrality
- Structural Complexity

CA identifies two primary components when investigating the effect
curricula has on students as they complete a degree: *structural
complexity* and *instructional complexity*. Structural
complexity has to do with how courses are ordered in sequence and more
specifically how requisite relationships between course orderings impact
student success. Instructional complexity refers to the quality of
instructors, support resources, etc. CA postulates that the complexity
of a given curriculum \(c\) will be a
function of these two complexities: \[\Psi_c
= f(\alpha_c,\gamma_c)\] where

- \(\gamma_c=g(\bar{x}_c)\) represents the instructional complexity which is itself a function of a vector \(\bar{x}_c\) containing instructional factors.
- \(\alpha_c = h(\bar{y}_c)\) represents the structural complexity which is itself a function of a vector \(\bar{y}_c\) containing structural factors.

CA’s main emphasis is in approximating \(\alpha_c\) and altering curricula to minimize structural complexity. For more reading on CA and its applications see [1].

Curricula are represented as *directed acyclic graphs* (DAGs).
Nodes represent courses and directed edges exist between nodes that hold
pre- or co-requisite relationships. Mathematically, for a curriculum
\(c\) that hold \(n\) courses we construct a *curriculum
graph* \(G_c=(V,E)\) where each
\(v \in V = \{v_1,\dots,v_n\}\)
represents a course and a directed edge \((v_i, v_j) \in E\) exists if course \(v_i\) must be completed previous or in
conjunction with \(v_j\). Note, no
distinction is made between a co- and pre-requisite.

Paths in a curriculum graph will be denoted as follows: a path \(p \in G_c = (V,E)\) is written \(v_i \overset{p}{\to} v_j\) where \(v_i,v_j \in V\). A path is simply a sequence of vertices \(\langle v_i,\dots,v_j \rangle\) where if \(v_x\) comes before \(v_y\) then \((v_x,v_y) \in E\). Additionally, \(\#(v_i \overset{p}{\to} v_j)\) is used to represent the number of nodes on a path.

The delay factor of a course is the longest path the nodes finds itself on. More formally the delay factor of a node \(v_k\) is given by

\[d_c(v_k)=\underset{i,j,l,m}{max}\left\{\#\left(v_i \overset{p_l}{\to} v_k \overset{p_m}{\to} v_j \right)\right\}\] The delay factor of an entire curriculum graph \(G_c\) is defined as

\[d(G_c)=\sum_{v_k \in V}d_c(v_k)\]

Blocking factor quantifies when a failing a course would result in being blocked from registering for future courses. More formally the blocking factor of a node \(v_i\) is defined as

\[b_c(v_i) = \sum_{v_j \in V} I(v_i,v_j)\] where \(I\) is the indicator function:

\[=\begin{cases}1, & \text{if } v_i \to v_j \\ 0, & \text{if }v_i \cancel{\to} v_j\end{cases}\] The blocking factor for an entire curriculum graph \(G_c\) is defined as

\[b(G_c)=\sum_{v_i \in V} b_c(v_i)\]

A course is considered central if it has many requisite edges flowing in and out of the node. More formally it is the number of long paths that include the node. That is, consider a curriculum graph and a vertex \(v_i\). A long path is a path that satisfies the following conditions:

- \(v_i,v_j,v_k\) are distinct
- \(v_j \to v_i \to v_k\)
- \(v_j\) is a source node (in-degree zero)
- \(v_k\) is a sink node (out-degree zero)

Let \(P_{v_i}=\{p_1,p_2,\dots\}\) denote the set of all paths defined as above. Then the centrality of a node \(v_i\) is given by

\[q(v_i)=\sum^{|P_{v_i}|}_{l=1}\#(p_l)\]

More plainly, this is the number of paths containing \(v_i\) of at least length 3 where \(v_i\) is neither source nor sink node.

The structural complexity of a node \(v_k\) is defined as a linear combination of the node’s delay and blocking factors. More formally

\[h(v_k) = d(v_k) + b(v_k)\] The structural complexity of an entire curriculum graph \(G_c\) is defined as

\[h(G_c)=d(G_c)+b(G_c)=\sum_{v_k \in V} \left(d_c(v_k) + b_c(v_k)\right)\]

The following examples present how one may use
**CurricularAnalytics** to analyze university curricula and
inform curriculum revision and creation. The analysis is conducted on
the Data Science (DS) curriculum found at the University of British
Columbia - Okanagan (UBCO). As Data Science is a rapidly evolving
discipline, the program has gone through many iterations with several
major overhauls having been implemented in the last five years. The
analysis investigates the 2022 and 2023 DS majors, the 2022 DS minor,
and the proposal of a math stream in the 2022 DS major.

Within the final years of the DS program at UBCO, students have a wide selection of DS-related elective credits. The rational behind this being that DS is a very broad field which allows students to acquire key core skills in their first two and a half years and then proceed to focus on subject areas that interest them the most.

The result, however, is an increase in the variability of curriculum graphs. Therefore, we propose to investigate the maximally and minimally structurally complex graphs. This allows the analyst to place upper and lower bounds on the structural complexity and see how degree pathways interact at the extremes. Additionally, we omit any general electives as we wish to examine the core structure of the curriculum graph.

In **CurricularAnalytics**, curriculum graph objects may
be created and stored as lists or CSVs. If stored as a CSV, one may load
their graph through `curriculum_graph_from_csv()`

. If the
user opts to create node and edge lists, they may call
`curriculum_graph_from_list()`

to create a curriculum
graph.

CSVs are specified as follows:

- id: an integer id for the course
- label: a string with the name of the course
- term: an integer specifying what term the course is to be taken
- requisites: a list of all pre- and co-requisite course ids of the form 1;2;3;…

e.g.,

id | label | term | requisities |
---|---|---|---|

1 | MATH 100 | 1 | NA |

2 | DATA 101 | 1 | NA |

3 | MATH 101 | 2 | 1 |

4 | MATH 221 | 2 | 3 |

5 | STAT 230 | 3 | 3;2 |

```
# Example creating C from CSV file
C <- curriculum_graph_from_csv("./data/DS-Major-Max-2023-2024.csv")
```

To create graphs using node and edge dataframes we do the following

```
# Example creating C from node and edge lists:
node_list <- C$node_list[,1:3]
edge_list <- C$edge_list
# Printing example of what such lists look like
print(head(node_list))
#> id label term
#> 1 1 DATA 101 1
#> 2 2 MATH 100 1
#> 3 3 PHYS 111 1
#> 4 4 COSC 111 1
#> 5 5 ENGL 151 1
#> 6 6 ENGL 113 2
```

```
C <- curriculum_graph_from_list(node_list, edge_list)
# plot_curriculum_graph curriculum graph
plot_curriculum_graph(C)
```

These graphs are fully interactable allowing you to move nodes around and click on nodes to explore their values. At the top, we see totals for structural complexity, blocking factor, and delay factor. These metrics speak to the overall complexity of the curriculum graph. Clicking a node reveals its structural complexity (sc), centrality (cf), blocking factor (bf), and delay factor (df).

Now we move onto the example analyses. Let us first examine our maximally complex curriculum graph. To do so we generate 1000 curriculum graphs from DS program requirements. There will be much variation in graphs and the most and least structurally complex graphs will likely not be unique. We do not have support in this package for generating max and min curriculum graphs as programs are so variable across universities.

The total structural complexity is 217, the total blocking factor is 96, and the total delay factor is 121. From here we wish to investigate further and the learn the set of course contributing the most to these metrics.

We first list the top course for each metric

```
# Define helper function for printing courses
print_top_two_rows <- function(df, column) {
ordered_df <- df[rev(order(df[[column]])), ]
top_two <- head(ordered_df, 3)
print(top_two)
}
# Print top two courses ordered by each metric
columns <- colnames(C_max$node_list[,c("bf","df","cf","sc")])
for (column in columns) {
print(paste("Ordering by column:", column))
print_top_two_rows(C_max$node_list, column)
}
#> [1] "Ordering by column: bf"
#> id label term bf df cf sc
#> 3 3 MATH 100 1 18 6 0 24
#> 9 9 COSC 111 1 16 6 0 22
#> 4 4 MATH 101 2 16 6 134 22
#> [1] "Ordering by column: df"
#> id label term bf df cf sc
#> 27 27 DATA 410 6 0 6 0 6
#> 21 21 STAT 400 5 0 6 0 6
#> 17 17 DATA 311 4 2 6 82 8
#> [1] "Ordering by column: cf"
#> id label term bf df cf sc
#> 13 13 STAT 230 3 9 6 266 15
#> 14 14 COSC 221 3 10 6 184 16
#> 4 4 MATH 101 2 16 6 134 22
#> [1] "Ordering by column: sc"
#> id label term bf df cf sc
#> 3 3 MATH 100 1 18 6 0 24
#> 9 9 COSC 111 1 16 6 0 22
#> 4 4 MATH 101 2 16 6 134 22
```

The most central nodes are STAT 230 (266) and COSC 221 (184). From visual inspection we can see STAT 230 is far too central as almost every edge to an advanced statistics or data science course has a path including STAT 230. STAT 230 is the primary introductory statistics course taken by students in the DS program. COSC 221 is Discrete Math and since it serves as a pre-requisite to STAT 230 it seems to take also take on the burden of being an overly central course. This suggests structural revision is needed in STAT 230.

The courses with the greatest blocking factors are MATH 100 (18), MATH 101 (16), and COSC 111 (16). These are Calculus 1, Calculus 2, and Computer Programming 1 respectively. The inflated blocking factors of our introductory math courses are an unfortunate and common symptom of many STEM programs where Calculus 1 and 2 act as gateways into almost all course pathways. This is often why failing these introductory courses is so detrimental to on-time graduation. This suggests increased resources towards MATH 100 and MATH 101 are warranted and if possible, structural revision. Though this pattern is likely unavoidable and therefore so is structural intervention.

The courses with the greatest delay factors are MATH 100, MATH 101, COSC 111, COSC 121, STAT 230, COSC 221, DATA 311, STAT 400, and DATA 410 with a value of 6. These courses find themselves a part of the longest pathways in the graph and their high delay factor speaks to the fact that if students wish to take upper year STAT and DATA courses they must navigate these long pathways. We wish for students to graduate on-time and so these high delay factor pathways are certainly areas that warrant further investigation.

The courses with the largest structural complexity are MATH 100 (24), MATH 101 (22), COSC 111 (22). Again a common theme is having these introductory Calculus courses act as “weeder” courses and, unfortunately, within STEM curricula this leads to these course being the most frequent contributors to high structural complexity scores. Similarly, COSC 111 is an introductory programming course that provides much of the needed computing foundation required for statistical analyses.

Through the examination of this graph we now have several areas of potential revision. STAT 230 certainly requires the greatest overhaul. When patterns such as STAT 230 are observed we often seek to split this course into two and divide up the requisite relationships. Furthermore, courses that find themselves with high delay factors at the ends of pathways, such as DATA 410, could likely have their prerequisite consolidated into fewer courses and so this will be examined.

Next we investigate the minimally complex curriculum graph.

The total structural complexity is 150, the total blocking factor is 58, and the total delay factor is 92. This is quite a bit lower than the maximally complex graph and indicates a sizable range in structural complexity depending on student elective choice.

Listing the top courses for each metric once more:

```
# Define helper function for printing courses
print_top_two_rows <- function(df, column) {
ordered_df <- df[rev(order(df[[column]])), ]
top_two <- head(ordered_df, 3)
print(top_two)
}
# Print top two courses ordered by each metric
columns <- colnames(C_min$node_list[,c("bf","df","cf","sc")])
for (column in columns) {
print(paste("Ordering by column:", column))
print_top_two_rows(C_min$node_list, column)
}
#> [1] "Ordering by column: bf"
#> id label term bf df cf sc
#> 3 3 MATH 100 1 16 5 0 21
#> 4 4 MATH 101 2 14 5 56 19
#> 8 8 COSC 111 1 7 3 0 10
#> [1] "Ordering by column: df"
#> id label term bf df cf sc
#> 20 20 STAT 403 5 0 5 0 5
#> 18 18 STAT 303 4 1 5 12 6
#> 10 10 MATH 200 3 2 5 16 7
#> [1] "Ordering by column: cf"
#> id label term bf df cf sc
#> 12 12 STAT 230 3 6 4 60 10
#> 4 4 MATH 101 2 14 5 56 19
#> 11 11 MATH 221 2 2 4 20 6
#> [1] "Ordering by column: sc"
#> id label term bf df cf sc
#> 3 3 MATH 100 1 16 5 0 21
#> 4 4 MATH 101 2 14 5 56 19
#> 12 12 STAT 230 3 6 4 60 10
```

The most central nodes are STAT 230 (60), MATH 101 (56), MATH 221 (20). Once Again STAT 230 is the most central node and from visual inspection we see quite a few incoming and outgoing edges. MATH 101 also appears as the second most central node likely due to being a prerequisite to STAT 230. Interestingly, MATH 221 finds itself being quite central in this curriculum. Perhaps further investigation into how we can reform connections including MATH 221 could be explored.

The highest blocking factor courses are MATH 100 (16), MATH 101 (14), COSC 111 (7).

The highest delay factor courses are MATH 100, MATH 101, MATH 200, STAT 303, and STAT 403 with a a factor of 5. Perhaps altering STAT 303 can help reduce long paths in this graph.

The courses with the largest structural complexity are MATH 100 (21), MATH 101 (19), STAT 230 (10).

It may also be prudent to examine specifically which courses differ between the two graphs to better understand what elective choices contribute to changes in structural complexity.

The courses in the max graph that are not found in the min graph are:

Courses |
---|

ENGL 113 |

ENGL 154 |

STAT 400 |

STAT 401 |

COSC 329 |

COSC 344 |

DATA 405 |

DATA 410 |

The courses in the min graph that are not found in the max graph are:

Courses |
---|

ENGL 109 |

STAT 403 |

COSC 407 |

COSC 421 |

MATH 307 |

PHYS 420 |

DATA 407 |

This highlights how upper year course selection affects complexity measures. Course like DATA 410, as identified earlier, have many prerequisites and are a part of the longest pathway of 6 in the graph. Upper year electives, such as COSc 407, have only 1 prerequisite making their entry easier and thus reducing complexity scores.

The 2023/2024 school year saw a major overhaul of the UBCO DS program. In investigating the 2022 - 2023 DS Major curriculum graphs we identified several courses and requisite relationships of interest. Let us implement some of these changes for the DS overhaul and see how our structural complexity is impacted.

The total structural complexity is 239, the total blocking factor is 100, and the total delay factor is 139. At first glance it may seem alarming that our maximally complex graph has had a large structural complexity increase. However, in the new DS program we have specified more required courses. In the previous curriculum students were given more flexibility to choose electives which we omitted from our previous graphs. Had we included these electives we likely would have seen similar numbers as we do currently.

Listing the top course for each metric yields

```
columns <- colnames(C_max$node_list[,c("bf","df","cf","sc")])
for (column in columns) {
print(paste("Ordering by column:", column))
print_top_two_rows(C_max$node_list, column)
}
#> [1] "Ordering by column: bf"
#> id label term bf df cf sc
#> 2 2 MATH 100 1 22 6 0 28
#> 3 3 MATH 101 2 20 6 130 26
#> 1 1 DATA 101 2 12 5 0 17
#> [1] "Ordering by column: df"
#> id label term bf df cf sc
#> 32 32 DATA 410 6 0 6 0 6
#> 25 25 STAT 400 5 0 6 0 6
#> 24 24 DATA 315 4 1 6 23 7
#> [1] "Ordering by column: cf"
#> id label term bf df cf sc
#> 16 16 STAT 203 3 11 6 159 17
#> 17 17 STAT 205 3 7 6 140 13
#> 3 3 MATH 101 2 20 6 130 26
#> [1] "Ordering by column: sc"
#> id label term bf df cf sc
#> 2 2 MATH 100 1 22 6 0 28
#> 3 3 MATH 101 2 20 6 130 26
#> 16 16 STAT 203 3 11 6 159 17
```

The most central nodes are STAT 203 (159), STAT 205 (140), and MATH 101 (130). In our new curriculum, STAT 230 has been split into two new courses each tackling different aspects of the original course. STAT 203 is the more theory heavy statistics course that leads into STAT 303, Intermediate Probability, and STAT 401, Statistical Inference. STAT 205 is more applied and includes R programming which is why it serves as the prerequisite for courses like DATA 311, Machine Learning, and DATA 310, Applied Regression Analysis. This structure is preferable as instead of having STAT 230 on every long path through the curriculum we have two fairly central courses that split the long pathways between them.

The courses with the greatest blocking factors are MATH 100 (22), MATH 101 (20), and DATA 101 (12). MATH 100 and 101, being Calculus 1 and 2, retain their high blocking factors. We’ve added DATA 101 as a prerequisite to STAT 203 which explains its rise in blocking factor compared to previous graphs.

The courses with the greatest delay factors are MATH 100, MATH 101, STAT 203, STAT 205, DATA 311, DATA 315, DATA 310, STAT 400, and DATA 410 with a delay factor of 6. This is similar to the previous graphs.

The courses with the largest structural complexity are unsurprisingly MATH 100 (28), MATH 101 (26), and STAT 203 (17). These 3 courses lead into every major course in the curriculum thus they contribute the most to structural complexity.

The key takeaway from this graph is how the splitting of STAT 230 from one relatively high central course into two relative medium central courses has improved the flow of the curriculum.

Once again we invesgitate the minmaly complex graph for the new DS program.

The total structural complexity is 193, the total blocking factor is 77, and the total delay factor is 116. A difference of 46 points in structural complexity compared to the maximally complex graph. This range is quite a bit smaller than the previous curriculum which had a difference of 67 points between the minimally and maximally complex graphs. Next we view individual course metrics.

```
# Print top two courses ordered by each metric
columns <- colnames(C_max$node_list[,c("bf","df","cf","sc")])
for (column in columns) {
print(paste("Ordering by column:", column))
print_top_two_rows(C_max$node_list, column)
}
#> [1] "Ordering by column: bf"
#> id label term bf df cf sc
#> 2 2 MATH 100 1 22 6 0 28
#> 3 3 MATH 101 2 20 6 130 26
#> 1 1 DATA 101 2 12 5 0 17
#> [1] "Ordering by column: df"
#> id label term bf df cf sc
#> 32 32 DATA 410 6 0 6 0 6
#> 25 25 STAT 400 5 0 6 0 6
#> 24 24 DATA 315 4 1 6 23 7
#> [1] "Ordering by column: cf"
#> id label term bf df cf sc
#> 16 16 STAT 203 3 11 6 159 17
#> 17 17 STAT 205 3 7 6 140 13
#> 3 3 MATH 101 2 20 6 130 26
#> [1] "Ordering by column: sc"
#> id label term bf df cf sc
#> 2 2 MATH 100 1 22 6 0 28
#> 3 3 MATH 101 2 20 6 130 26
#> 16 16 STAT 203 3 11 6 159 17
```

The courses with the highest centrality are STAT 203 (159), STAT 205 (140), and MATH 101 (130). This is the same set of courses as before.

The courses with the highest blocking factor are MATH 100 (22), MATH 101 (20), and DATA 101 (12). These courses also appear the same as before. It is interesting DATA 101 appears on this list again further cementing its new found importance in this revised curriculum.

The highest delay factor courses are MATH 100, MATH 101, STAT 203, STAT 205, DATA 311, DATA 310, DATA 315, STAT 400, and DATA 410 at 6. Many of the same courses are a part of the longest paths.

The courses with the largest structural complexity are MATH 100 (28), MATH 101 (26), and STAT 203 (17).

Again it is interesting to see the difference in courses between the max and min curriculum graphs. The courses in the max graph not found in the min graph are:

Courses |
---|

PHYS 122 |

BIOL 125 |

PHYS 111 |

STAT 400 |

COSC 344 |

MATH 303 |

MATH 409 |

DATA 410 |

The courses in the min graph that are not found in the max graph are:

Courses |
---|

CHEM 121 |

STAT 401 |

COSC 322 |

COSC 421 |

MATH 307 |

MATH 327 |

Data Science minors are far more common at UBCO than the major. Therefore examination of the minor could prove impactful as its restructuring will affect more students. We explore the maximally and minimally complex minor curriculum graphs.

Minors are only 30 credits. In creating minor curriculum graphs we cannot simply sample 30 credits worth of permissible courses as this will exclude important prerequisite courses. We wish to examine how courses flow into one another therefore we sample 30 credits of minor courses and represent these nodes as circles. We then add triangles for prerequisite courses that are not counted towards the minor but would still be required to take if the student wished to complete the minor counting courses.

Below we demonstrate how one can extract the igraph network
curriculum graph object and have more control over the visualization
using `visNetwork`

. We begin with the maximally structurally
complex graph.

```
library(visNetwork)
# Create Curriculum Graph
C <- curriculum_graph_from_csv("./data/DS-Minor-Max.csv")
C$node_list <- C$node_list[order(C$node_list$term), ]
# Specify shape and group for each node
C$node_list$shape <- c(rep("circle",10),rep("triangle",16))
C$node_list$group <- c(rep("FALSE",10),rep("TRUE",16))
# Helper function to generate coordinates
generate_coords <- function(curriculum_graph) {
coords <- matrix(ncol = 2)
old_term <- 1
idx <- -1
for (term in curriculum_graph$node_list$term) {
if (old_term != term) {
idx <- 0
old_term <- term
} else{
idx <- idx + 1
}
coords <- rbind(coords, c(term, idx))
}
coords <- stats::na.omit(coords)
return(coords)
}
# Create fully customizable plot_curriculum_graph
visNetwork(
C$node_list,
C$edge_list,
height = 700,
width = 700,
submain = paste(
"Total Structural Complexity:",
C$sc_total,
"Total Blocking Factor:",
C$bf_total,
"Total Delay Factor:",
C$df_total
)
) %>%
visEdges(arrows = "to") %>%
visIgraphLayout(layout = "layout.norm", layoutMatrix = generate_coords(C)) %>%
visEvents(
selectNode = "function(properties) {
alert(' sc: ' + this.body.data.nodes.get(properties.nodes[0]).sc + ' cf: ' + this.body.data.nodes.get(properties.nodes[0]).cf + ' bf: ' + this.body.data.nodes.get(properties.nodes[0]).bf + ' df: ' + this.body.data.nodes.get(properties.nodes[0]).df);}"
)%>%
visGroups(groupname = "TRUE", color = "red") %>%
visGroups(groupname = "FALSE", color = "lightblue") %>%
visLegend(width = 0.1, position = "right", main = "Is a Preqreq in the Minor")
```

This graph is quite complex because it has chosen to display both the Applied Science and Psychology routes into the DS minor. What is interesting here is the presence of red arrows to blue nodes. Course with many red in-edges suggest that they would be more difficult to take in the minor as they require many courses outside of what would be counted. Likely such courses are only available to students in DS-adjacent programs such as Computer Science or Statistics.

Next is the minimally structurally complex graph.

```
# Create Curriculum Graph
C <- curriculum_graph_from_csv("./data/DS-Minor-Min.csv")
C$node_list <- C$node_list[order(C$node_list$term), ]
# Specify shape and group for each node
C$node_list$shape <- c(rep("circle",9),rep("triangle",3))
C$node_list$group <- c(rep("FALSE",9),rep("TRUE",3))
visNetwork(
C$node_list,
C$edge_list,
height = 500,
width = 500,
submain = paste(
"Total Structural Complexity:",
C$sc_total,
"Total Blocking Factor:",
C$bf_total,
"Total Delay Factor:",
C$df_total
)
) %>%
visEdges(arrows = "to") %>%
visIgraphLayout(layout = "layout.norm", layoutMatrix = generate_coords(C)) %>%
visEvents(
selectNode = "function(properties) {
alert(' sc: ' + this.body.data.nodes.get(properties.nodes[0]).sc + ' cf: ' + this.body.data.nodes.get(properties.nodes[0]).cf + ' bf: ' + this.body.data.nodes.get(properties.nodes[0]).bf + ' df: ' + this.body.data.nodes.get(properties.nodes[0]).df);}"
)%>%
visGroups(groupname = "TRUE", color = "red") %>%
visGroups(groupname = "FALSE", color = "lightblue") %>%
visLegend(width = 0.1, position = "right", main = "Is a Preqreq in the Minor")
```

This is the minor in its absolute simplest form. What will be of interest in this graph is which courses appear. This is because it is the easiest way for a general science student to obtain the minor and thus it is likely going to be a popular pathway.

STAT 230 is a prerequisite to nearly half of the minor counting courses. Its centrality was high in the previous major graphs but here its impact is arguably greater since there are so few courses a student can take.

The presence of STAT 303 really only serves to increase the complexity as it forces the addition of MATH 200 and does not flow into any other courses itself. This could suggest we require a different course.

Next, we demonstrate how **CurricularAnalytics** can
help in creating new curricula. Inventing new programs or concentrations
can be quite difficult as there are often many moving pieces the
curriculum designer must consider. Through its ability to visualize and
quantify degree composition, our package allows the user to explore
potential structures and consider “what-ifs?” with greater ease.

Currently many math electives are available to DS students as of the 2022/2023 academic calendar. What would a DS major look like if we offered a math stream requiring all math electives?

First the maximally complex graph:

```
# Create curriculum graph
C <- curriculum_graph_from_csv("./data/DS-Major-Math-Max.csv")
# plot_curriculum_graph curriculum graph
plot_curriculum_graph(C)
```

Immedately we are able to visualize what such a concentration may look like. From inspection it looks like there is higher edge density around MATH 221 and MATH 200. MATH 101 seems to feed into a majority of the courses and MATH 409 looks quite difficult to get to. Next we can look at our metrics to quantify these thoughts.

```
# Print top two courses ordered by each metric
columns <- colnames(C$node_list[,c("bf","df","cf","sc")])
for (column in columns) {
print(paste("Ordering by column:", column))
print_top_two_rows(C$node_list, column)
}
#> [1] "Ordering by column: bf"
#> id label term bf df cf sc
#> 3 3 MATH 100 1 17 5 0 22
#> 9 9 MATH 101 2 16 5 100 21
#> 4 4 COSC 111 1 8 3 0 11
#> [1] "Ordering by column: df"
#> id label term bf df cf sc
#> 29 29 MATH 409 6 0 5 0 5
#> 27 27 STAT 401 6 0 5 0 5
#> 26 26 DATA 410 6 0 5 0 5
#> [1] "Ordering by column: cf"
#> id label term bf df cf sc
#> 9 9 MATH 101 2 16 5 100 21
#> 11 11 MATH 200 3 6 5 57 11
#> 13 13 STAT 230 3 5 5 53 10
#> [1] "Ordering by column: sc"
#> id label term bf df cf sc
#> 3 3 MATH 100 1 17 5 0 22
#> 9 9 MATH 101 2 16 5 100 21
#> 11 11 MATH 200 3 6 5 57 11
```

We find that MATH 101 is the most central at a centrality factor of (83). MATH 419 is a part of the highest delay factor nodes agreeing with our inital thought that it could be a difficult course to reach in a math stream. Unsurpsringly MATH 100 and MATH 101 have the high blocking factors and and structural complexities as they are the primary gateway into this variation of the program.

Next the minimally complex graph:

```
# Create curriculum graph
C <- curriculum_graph_from_csv("./data/DS-Major-Math-Min.csv")
# plot_curriculum_graph curriculum graph
plot_curriculum_graph(C)
```

We don’t see a very large shift in total structural complexity, a difference of only 9, suggesting the math stream would be quite stable in terms of diffculty. There isn’t much room for elective choice and we see many of the same courses appear. Perhaps that observation could prompt us to restructure to allow for more electives. Once again we move onto investigating individual metrics.

```
# Print top two courses ordered by each metric
columns <- colnames(C$node_list[,c("bf","df","cf","sc")])
for (column in columns) {
print(paste("Ordering by column:", column))
print_top_two_rows(C$node_list, column)
}
#> [1] "Ordering by column: bf"
#> id label term bf df cf sc
#> 3 3 MATH 100 1 17 5 0 22
#> 8 8 MATH 101 2 16 5 83 21
#> 4 4 COSC 111 1 7 3 0 10
#> [1] "Ordering by column: df"
#> id label term bf df cf sc
#> 29 29 MATH 319 6 1 5 24 6
#> 26 26 MATH 409 6 0 5 0 5
#> 22 22 STAT 403 5 0 5 0 5
#> [1] "Ordering by column: cf"
#> id label term bf df cf sc
#> 8 8 MATH 101 2 16 5 83 21
#> 10 10 MATH 200 2 5 5 48 10
#> 12 12 STAT 230 3 4 5 43 9
#> [1] "Ordering by column: sc"
#> id label term bf df cf sc
#> 3 3 MATH 100 1 17 5 0 22
#> 8 8 MATH 101 2 16 5 83 21
#> 10 10 MATH 200 2 5 5 48 10
```

We find much of the same reiterating the stability of such a program.

In this vignette we give an introduction to our R implementation of Curricular Analytics, a powerful framework for quickly and effectively visualizing and quantifying curricula. Through its use, we are able to highlight problematic curriculum structuring and suggest data-driven revisions quantified through metrics such as blocking factor, delay factor, and course centrality.