# now let's create a new column for seconds
# 1 second = 25 frames
= gcs_sf |>
gcs_sf_s ::mutate(sec = as.numeric(frame) / 25)
dplyr
# group df by seconds
= gcs_sf_s |>
gcs_grouped ::st_drop_geometry() |> #drop geometry as it's not needed here
sf::group_by(sec) |> # group by seconds
dplyr::summarise(n = dplyr::n()) # summarise
dplyr
|>
gcs_grouped head()
4 Flow
Flow is one of the basic measures to quantify pedestrian dynamics (Steffen and Seyfried, 2010).
Flow is measured by counting the heads that pass through a line, e.g. doors, within a given time interval.
In this chapter we will find out three types of flow in Grand Central Station (GCS) and JuPedSim (JPS) model:
- Global, or the entire environment, flow
- Divided flow, which refers to the environment being divided into four equal polygons and measuring flow in each
- Selected flow refers to two manually defined areas – Zone 1 and Zone 2. Zone 1 has the density that is similar to that of the entire environment and Zone 2 has the highest density in the environment. For more information see chapter “Environment”.
4.1 1.1 Global flow (GCS)
4.1.1 Plotting
= ggplot2::ggplot(gcs_grouped) +
gcs_flow_plot ::aes(x = sec,
ggplot2y = n) +
::geom_line() ggplot2
We could add add additional information by adding lines indicating mean and median for seconds and number of agents, but I’m not sure it tells us much…
# let's calculate mean and median values of n to add to the plot
= gcs_sf_s$sec |> mean()
gcs_mean_s = gcs_sf_s$sec |> median()
gcs_median_s = gcs_grouped$n |> mean()
gcs_mean_n = gcs_grouped$n |> median()
gcs_median_n
::ggplot(gcs_grouped) +
ggplot2::aes(x = sec,
ggplot2y = n) +
::geom_line() +
ggplot2::geom_vline(xintercept = gcs_mean_s,
ggplot2col = "red") +
::geom_text(ggplot2::aes(x=gcs_mean_s+5, label=paste0("Mean\n",round(gcs_mean_s,2)), y=80)) +
ggplot2::geom_vline(xintercept = gcs_median_s,
ggplot2col = "blue") +
::geom_hline(yintercept = gcs_mean_n,
ggplot2col = "red") +
::geom_hline(yintercept = gcs_median_n,
ggplot2col = "blue")
4.2 2.1 Divided flow (GCS)
# first create a list to store our multiple dataframes
= list()
gcs_joined for (i in 1:lengths(gcs_div_sf)){
= gcs_sf_s[gcs_div_sf[i,], op = sf::st_intersects] # all intersecting points will be selected
gcs_joined[[i]]
}
# sanity check
= gcs_sf_s[gcs_div_sf[1,], op = sf::st_intersects]
gcs_joined1 = gcs_sf_s[gcs_div_sf[2,], op = sf::st_intersects]
gcs_joined2 identical(gcs_joined1, gcs_joined[[1]]) # TRUE
identical(gcs_joined2, gcs_joined[[2]]) # TRUE
# group each sf object by seconds and make a list out of them
= list()
gcs_joined_grouped for (i in 1:length(gcs_joined)){
= gcs_joined[[i]] |>
gcs_joined_grouped[[i]] ::st_drop_geometry() |>
sf::group_by(sec) |>
dplyr::summarise(n = dplyr::n())
dplyr
}
# sanity check comparison (alert: ugly code!)
identical(gcs_joined_grouped[[1]], # first list of a list that was just made
1]] |> # repeating the same code as in the loop above but only on 1 (the first) list
gcs_joined[[::st_drop_geometry() |>
sf::group_by(sec) |>
dplyr::summarise(n = dplyr::n())) dplyr
4.2.1 Plotting
In the plot showing flow in the entire GCS environment, I added means and medians but this time I will exclude them as I do not know if it’s valuable to have them at this stage. Plus, it will make the code shorter.
# let's create a list of plots showing flow in each polygon
= list()
gcs_flow_div_plots for (i in 1:length(gcs_joined_grouped)){
= ggplot2::ggplot(gcs_joined_grouped[[i]]) +
gcs_flow_div_plots[[i]] ::aes(x = sec,
ggplot2y = n) +
::geom_line()
ggplot2# print(gcs_flow_div_plots)
}
# let's plot a polygons 1-4
::grid.arrange(gcs_flow_div_plots[[1]], gcs_flow_div_plots[[2]],gcs_flow_div_plots[[3]],gcs_flow_div_plots[[4]], layout_matrix = rbind(c(1,2),c(3,4))) gridExtra
4.3 3.1 Selected (GCS)
# a list to store our 2 dataframes for the selected areas
= list()
gcs_joined_zones for (i in 1:length(zones)){
= gcs_sf_s[zones[[i]], op = sf::st_intersects] # all intersecting points will be selected
gcs_joined_zones[[i]]
}
# sanity check
= gcs_sf_s[zones[[1]], op = sf::st_intersects]
gcs_joined_zones1 identical(gcs_joined_zones1, gcs_joined_zones[[1]]) # TRUE
# group each sf object by seconds and make a list out of them
= list()
gcs_joined_grouped_zones for (i in 1:length(gcs_joined_zones)){
= gcs_joined_zones[[i]] |>
gcs_joined_grouped_zones[[i]] ::st_drop_geometry() |>
sf::group_by(sec) |>
dplyr::summarise(n = dplyr::n())
dplyr
}
# sanity check comparison (alert: ugly code!)
identical(gcs_joined_grouped_zones[[1]], # first list of a list that was just made
1]] |> # repeating the same code as in the loop above but only on 1 (the first) list
gcs_joined_zones[[::st_drop_geometry() |>
sf::group_by(sec) |>
dplyr::summarise(n = dplyr::n())) dplyr
4.3.1 Plotting
# let's create a list of plots showing flow in each polygon
= list()
gcs_flow_zones_plots for (i in 1:length(gcs_joined_grouped_zones)){
= ggplot2::ggplot(gcs_joined_grouped_zones[[i]]) +
gcs_flow_zones_plots[[i]] ::aes(x = sec,
ggplot2y = n) +
::geom_line()
ggplot2# print(gcs_flow_zones_plots )
}
::grid.arrange(gcs_flow_zones_plots[[1]], gcs_flow_zones_plots[[2]], layout_matrix = rbind(c(1,2),c(3,4))) gridExtra
4.4 1.2 Global (JPS)
# now let's create a new column for seconds
# 1 second = 25 frames
= traj1_sf |>
jps_s ::mutate(sec = FR / 25)
dplyr
# group df by seconds
= jps_s |>
jps_grouped ::st_drop_geometry() |> #drop geometry as it's not needed here
sf::group_by(sec) |> # group by seconds
dplyr::summarise(n = dplyr::n()) # summarise
dplyr
|>
jps_grouped head()
4.4.1 Plotting
= ggplot2::ggplot(jps_grouped) +
jps_flow_plot ::aes(x = sec,
ggplot2y = n) +
::geom_line()
ggplot2 jps_flow_plot
4.5 2.2 Divided flow (JPS)
# first create a list to store our multiple dataframes
= list()
jps_joined for (i in 1:lengths(gcs_div_sf)){
= jps_s[gcs_div_sf[i,], op = sf::st_intersects] # all intersecting points will be selected
jps_joined[[i]]
}
# sanity check
= jps_s[gcs_div_sf[1,], op = sf::st_intersects]
jps_joined1 identical(jps_joined1, jps_joined[[1]]) # TRUE
# group each sf object by seconds and make a list out of them
= list()
jps_joined_grouped for (i in 1:length(jps_joined)){
= jps_joined[[i]] |>
jps_joined_grouped[[i]] ::st_drop_geometry() |>
sf::group_by(sec) |>
dplyr::summarise(n = dplyr::n())
dplyr
}
# sanity check comparison (alert: ugly code!)
identical(jps_joined_grouped[[1]], # first list of a list that was just made
1]] |> # repeating the same code as in the loop above but only on 1 (the first) list
jps_joined[[::st_drop_geometry() |>
sf::group_by(sec) |>
dplyr::summarise(n = dplyr::n())) dplyr
4.5.1 Plotting
# let's create a list of plots showing flow in each polygon
= list()
jps_flow_div_plots for (i in 1:length(jps_joined_grouped)){
= ggplot2::ggplot(jps_joined_grouped[[i]]) +
jps_flow_div_plots[[i]] ::aes(x = sec,
ggplot2y = n) +
::geom_line()
ggplot2# print(gcs_flow_div_plots)
}
# let's plot polygons 1-4
::grid.arrange(jps_flow_div_plots[[1]], jps_flow_div_plots[[2]],jps_flow_div_plots[[3]],jps_flow_div_plots[[4]], layout_matrix = rbind(c(1,2),c(3,4))) gridExtra
4.6 3.2 Selected (JPS)
# a list to store our 2 dataframes for the selected areas
= list()
jps_joined_zones for (i in 1:length(zones)){
= jps_s[zones[[i]], op = sf::st_intersects] # all intersecting points will be selected
jps_joined_zones[[i]]
}
# sanity check
= jps_s[zones[[1]], op = sf::st_intersects]
jps_joined_zones1 identical(jps_joined_zones1, jps_joined_zones[[1]]) # TRUE
# group each sf object by seconds and make a list out of them
= list()
jps_joined_grouped_zones for (i in 1:length(jps_joined_zones)){
= jps_joined_zones[[i]] |>
jps_joined_grouped_zones[[i]] ::st_drop_geometry() |>
sf::group_by(sec) |>
dplyr::summarise(n = dplyr::n())
dplyr
}
# sanity check comparison (alert: ugly code!)
identical(jps_joined_grouped_zones[[1]], # first list of a list that was just made
1]] |> # repeating the same code as in the loop above but only on 1 (the first) list
jps_joined_zones[[::st_drop_geometry() |>
sf::group_by(sec) |>
dplyr::summarise(n = dplyr::n())) dplyr
4.6.1 Plotting
# let's create a list of plots showing flow in each polygon
= list()
jps_flow_zones_plots for (i in 1:length(jps_joined_grouped_zones)){
= ggplot2::ggplot(jps_joined_grouped_zones[[i]]) +
jps_flow_zones_plots[[i]] ::aes(x = sec,
ggplot2y = n) +
::geom_line()
ggplot2# print(gcs_flow_zones_plots )
}
::grid.arrange(jps_flow_zones_plots[[1]], jps_flow_zones_plots[[2]], layout_matrix = rbind(c(1,2),c(3,4))) gridExtra
4.7 Comparison
In this subsection we will overlay GCS and JPS plots to compare flows.
4.7.1 Global
= gcs_flow_plot
flow_comp ::geom_line(data = jps_grouped,
ggplot2color = "red")
4.7.2 Divided
= list()
flow_div_comp for (i in 1:length(gcs_flow_div_plots)) {
= gcs_flow_div_plots[[i]] +
flow_div_comp[[i]] ::geom_line(data = jps_joined_grouped[[i]],
ggplot2color = "red")
}
::grid.arrange(flow_div_comp[[1]], flow_div_comp[[2]],flow_div_comp[[3]],flow_div_comp[[4]], layout_matrix = rbind(c(1,2),c(3,4))) gridExtra
4.7.3 Selected
= list()
flow_sel_comp for (i in 1:length(gcs_flow_zones_plots)) {
= gcs_flow_zones_plots[[i]] +
flow_sel_comp[[i]] ::geom_line(data = jps_joined_grouped_zones[[i]],
ggplot2color = "red")
}
::grid.arrange(flow_sel_comp[[1]], flow_sel_comp[[2]], layout_matrix = rbind(c(1,2))) gridExtra
## Discussion
JPS model (simulated data) has higher flow rate compared to GCS (real data) in all plots. A potential reason for this is that in the JPS model new agents are “injected” into the environement at defined periods of time, which might not be representative of the actual pedestrian dynamics in the concourse. In other words, it is likely that in the real life pedestrians enter and exit the environment based on the train schedule, thus ensuring more or less stable in and out-flow rates. This might not be captured in the model, thus resulting in a peak that is not present in the GCS data.
Importantly, however, we can see that in the plots representing the flow in the selected areas (Zone 1 and 2) the rate is what was expected: Zone 1 is has lower agent flow compared to Zone 2. In this respect the JPS model seems pretty well calibrated even if, in tota, it has higher flow rate.