Weather Conditions and their Influence on Human Mobility Patterns
GEO 880 - Computational Movement Analysis - Final Project
Author
Lars Weidinger & Javier Feller
Published
June 21, 2026
Abstract
Ensuring safe and efficient pedestrian mobility requires a close understanding of its influencing factors. Weather conditions, particularly precipitation and temperature, are among important external factors affecting walking behaviour. In light of climate change with increasing temperatures and changing variability of precipitation, capturing these effects becomes increasingly important. Based on a case study of Zurich, this study examines how precipitation and temperature (external factors) influence both the spatial mobility extent (navigation capacity) and walking speed (motion capacity).
The results suggest that increasing temperature can lead to higher walking speed, except under very cold conditions. Furthermore, the spatial extent decreases with longer precipitation duration. While the data points are too few to detect significant relationships, this analysis shows initial evidence of weather-related effects on pedestrian mobility and serves as basis for further, more in-depth studies.
Introduction
Since the dawn of human evolution, humans have walked across the surface of the earth. To this day, walking remains the most essential mode of transport, since almost all trips include some part of moving on foot (Giannoulaki and Christoforou (2024)). Pedestrian movement has therefore been studied in depth to create appealing and efficient walking infrastructure.
One of the most relevant variables in pedestrian movement is walking speed. According to Giannoulaki and Christoforou (2024), it depends on five factors: flow characteristics, pedestrian attributes, layout configuration, ambient conditions and behavioural patterns. This project focuses on ambient conditions, namely weather (temperature, precipitation), and its effect on pedestrian mobility patterns. As conceptual movement space, we use an unconstrained movement model (Laube (2009)), since walking mobility does not always occur on fixed routes.
Following the established framework for context-aware Movement Analysis (CAMA) Nathan et al. (2008), we investigate precipitation and temperature effects (external factors) on spatial extent (navigation capacity) and on speed levels (motion capacity), noting that weather can affect motion capacity both directly and indirectly.
In the northern hemisphere, low temperatures and precipitation are generally associated with poorer walking conditions, as rain, snow and ice reduce pavement quality and require more cautious, slower movement (Montigny, Ling, and Zacharias (2012)). However, the relationship is complex since pedestrians may also increase their speed under unfavourable weather to minimize exposure and reach their destination faster (Giannoulaki and Christoforou (2024)). Warmer temperatures up to a certain threshold encourage walking, while higher temperatures (23°C or more) may lead pedestrians to seek shade and alter their routes (Montigny, Ling, and Zacharias (2012)). The speed increase in heat was also found by Rotton, Shats, and Standers (1990), which is explained through the Yerkes–Dodson law, where moderate arousal (=heat) improves performance while excessive stress reduces it (a U-shaped curve) (Yerkes and Dodson (1908)). Furthermore, walking may also serve as a heat-production mechanism in cold temperatures, resulting in faster speeds (Shuichi et al. (2021)).
Precipitation is one of the strongest determinants of pedestrian activity. Rainfall significantly reduces pedestrian traffic, and this effect extends beyond the immediate weather event, as travellers may anticipate rainfall and adjust their mobility decisions in advance (Zhao et al. (2019)).
This project focuses on the impact of temperature and precipitation on pedestrian mobility patterns and walking behaviour, leading to the following Research Questions (RQ):
I) How does temperature affect walking speed?
II) How does precipitation affect walking speed?
III) How do temperature and precipitation affect the spatial extent of activity spaces?
IV) Do different temporal scales of the weather data matter?
Since previous research showed the importance and sensitivity of varying scale (Laube and Purves (2011), Zhao et al. (2019)), special attention is given to it in this analysis. For the weather station data, we account for the effect of spatial scale selection comparing city-level vs. station-level analysis as well as considering varying temporal scale with daily, hourly and 15 min weather data. Thus, we also perform a small cross-scale movement analysis. For the mobility data, we don’t include a cross-scale movement analysis, but discuss our choices and parameters extensively.
Material and Methods
Load Packages
Code
library(pacman)# Load and if not found in the library will attempt to install the packagepacman::p_load(here, sf, readr, tmap, dplyr, ggplot2, tidyr, jsonlite, # to import JSON files lubridate, # temporal import from JSON file tidyverse, leaflet, # interactive maps, more personal experience than on tmap tmap, # interactive maps concaveman, # construct concave hulls gstat, # for IDW stars, # to create a grid purrr, devtools, # for the wordcountaddin to work wordcountaddin, # counts words and charactersinstall =TRUE)
Import & Preprocess Data
preprocessing
source("preprocessing.R") # this runs the whole preprocessing file# See the preprocessing.R file where we import:# - weather data (weather_daily_df, weather_hourly_df, meteoblue_weather_data)# - weather station location data (weather_stations_sf, meteoblue_stations)# - tracking data (google_timeline_sf_lv95)# - city boundary data (stadtgrenze)# set CRScrs_lv95 <-2056crs_wgs84 <-4326
Since we need to assign the weather data to our GPS fixes and later our segments we need to relate the two datasets spatially. For that, we construct Voronoi polygons around the measuring stations. Later we can then assign each Google timeline fix the weather values from the station it is closest to (= inside the Voronoi polygon). This method is rather simple but very effective.
Code
# both datasets are in CH1903+# create a union of pointspts_union <-st_union(weather_stations_sf)# create Voronoi tessellation with an envelope# st_voronoi has an envelope argument. Instead of generating a huge (or to small) tessellation we can set the scope hereenvelope_stadtgrenze <-st_as_sfc(st_bbox(stadtgrenze))voronoi <-st_voronoi( pts_union,envelope = envelope_stadtgrenze)# extract polygonsvoronoi_polygons <-st_collection_extract(voronoi, "POLYGON")# convert to sf objectvoronoi_sf <-st_sf(ID = weather_stations_sf$ID,geometry =st_cast(voronoi_polygons))# clip to city boundaryvoronoi_clipped <- sf::st_intersection(voronoi_sf, stadtgrenze)
# We can now use a spatial join to assign the corresponding weather station to each of the Google timeline fixes.# both datasets are in CH1903+# spatial join: assign polygon attributes (ID) to pointsgoogle_timeline_sf_lv95 <-st_join( google_timeline_sf_lv95, voronoi_clipped["ID"],join = st_within) |># Since we traveled quite a lot outside of the city of Zurich, we omit the data# outside the city (NA values), which is approximately 60% of the data points.drop_na() # omit NA values (quite a lot of them)
Code
# visualise timeline pointsggplot() +geom_sf(data = stadtgrenze, fill ="#D5F4FF", color ="black") +geom_sf(data = google_timeline_sf_lv95, color ="orange", size =0.6) +coord_sf(datum = crs_lv95) +labs(title ="Google Timeline Data Points\n within Zurich",color ="Stop",x ="Easting [m]",y ="Northing [m]") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8),axis.text =element_blank(), axis.title =element_blank() )
Assign Weather Data to Google Timeline
Code
# Now that we have assigned each data point to its closest weather station, we need to assign them their relevant weather values. We decided on using temperature (T) and rain (RainDur). One problem is, that the station Heubeeribüel does not collect RainDur information.# based on the exercises of week 5, Task 3 ff we first create a join key so that we can later match them exactlygoogle_timeline_sf_lv95 <- google_timeline_sf_lv95 |>mutate(Date =as.Date(time)) # since we only have one measurement per nightweather_daily_df <- weather_daily_df |>mutate(Date =as.Date(Datum)) # since we only have one measurement per night
Code
# prepare weather dataweather_daily_df_wide <- weather_daily_df |>filter(Parameter %in%c("T", "RainDur")) |># we only want T and RainDurselect(Date, Standort, Parameter, Wert) |>pivot_wider( # we need the parameters as columnsnames_from = Parameter,values_from = Wert ) |>rename(Temperature = T,RainDur = RainDur, ID = Standort )
Code
# join the two datasetsgoogle_timeline_weather <- google_timeline_sf_lv95 |>left_join( weather_daily_df_wide,by =c("Date", "ID") )
Code
# visualise timeline points with voronoi polygonsggplot() +geom_sf(data = voronoi_clipped, aes(fill = ID)) +geom_sf(data = google_timeline_sf_lv95, color ="orange", size =0.6) +coord_sf(datum = crs_lv95) +labs(title ="Google Timeline Data Points\n within the Voronoi Polygons",color ="Stop",x ="Easting [m]",y ="Northing [m]") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8),axis.text =element_blank(), axis.title =element_blank() )
Visualise Activity Space (Concave Hulls)
To answer RQ II we need to construct some sort of measurement for activity space. We decided on constructing concave hulls around the trips for each day. We then calculate the area of these hulls, take the average weather values across the whole hull and analyse if the area is larger when temperature is higher or rain duration is lower.
We decided to use concave hulls rather than convex hulls since they are less prone to outliers. Even though there are some convex hull implementations that allow for certain variables to be manipulated in order to exclude outliers (e.g. adehabitatHR::mcp()), the concave hull is more suited for our use case. We could have also used Alpha shapes (alphahull::) which accounts for holes in our data but this is not needed since we are dealing with continuous travel data (Weibel (2024)).
As an alternative, we could have used the Standard Devitational Ellipse (SDE), which forms a closed shape that while representing distributional properties of the point set it does not enclose all points. This would have been potentially more fitting, since the Google timeline data does not capture the movement between fixes, therefore single fixes could distort the concave hulls.
Construct Concave Hulls
Code
# In the first approach we called concaveman() on the entire sfc column (all geometries at once), not on each row individually. It computed one hull over all points and that same result gets recycled into every row which resulted in all the hulls being the same. We asked Claude AI (Model Sonnet 4.6) for help and it delivered the following solution using lapply.# Step 1: union points by dategoogle_timeline_hulls <- google_timeline_sf_lv95 |>group_by(Date) |>summarise(geometry =st_union(geometry), .groups ="drop") |>st_as_sf()# Step 2: build concave hull for each datehulls_list <-lapply(google_timeline_hulls$geometry, function(g) { coords <-st_coordinates(g)[, c("X", "Y")]# need at least 3 unique points for a polygonif (is.null(nrow(coords)) ||nrow(unique(coords)) <3) return(NA)st_polygon(list(concaveman(coords)))})# Step 3: check which dates failedfailed_dates <- google_timeline_hulls$Date[sapply(hulls_list, function(x) identical(x, NA))]print(failed_dates) # inspect which dates are problematic
# visualise concave hullsggplot() +geom_sf(data = stadtgrenze, fill ="transparent", color ="black") +geom_sf(data = google_timeline_hulls, aes(fill =as.factor(Date)), alpha =0.8) +scale_fill_discrete(guide ="none") +labs(title ="Concave Hulls of Daily Movement in Zurich",x ="Easting [m]",y ="Northing [m]") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8),axis.text =element_blank(), axis.title =element_blank() )
Assign Weather Values
Code
# average weather values across whole hulldaily_weather <- google_timeline_weather |>st_drop_geometry() |>group_by(Date) |>summarise(Temperature =mean(Temperature, na.rm =TRUE),RainDur =mean(RainDur, na.rm =TRUE) )daily_hulls_weather <- google_timeline_hulls |>left_join(daily_weather, by ="Date")
Code
# visualise concave hulls with weather dataggplot() +geom_sf(data = stadtgrenze, fill ="transparent", color ="black") +geom_sf(data = daily_hulls_weather[c(1, 8, 24, 35), ], aes(fill = Temperature), alpha =1) +scale_fill_gradient2(low ="blue", mid ="white", high ="red", midpoint =0, name ="Temperature [°C]") +labs(title ="Four Random Concave Hulls of Daily Movement in Zurich",color ="Temperature",x ="Easting [m]",y ="Northing [m]") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8),axis.text =element_blank(), axis.title =element_blank() )
Weather Daily (Field)
We only have weather measurements at at a handful of stations across the city. Assigning each location fix the weather data of the Voronoi polygon of the station is simplistic. Weather changes rather gradually across space, a more “fluid” approach would be better. That is why we decided on using Inverse Distance Weighting (IDW), which is a simple approach to generate gradually changing values across space using some sort of tessellation.
Inverse Distance Weighting (IDW)
Code
# With only few stations the interpolation accuracy will be low. We are not capturing real microclimate variation like urban heat island effects or cooling green spaces and water bodies. But since we plan on repeating our method with the finer scaled Meteoblue stations, we decided on applying the IDW method.# Since we do not want to compute a full raster for every day we will do a point-wise IDW prediction directly at each location fix's coordinates, using all weather station information. A raster per timestamp needs a lot of computation time and we only need the value at the fix's exact coordinates. Point-wise IDW is just a weighted average of all the distance, which takes much less time and we avoid avoid having to manage hundreds of rasters.# this code chunk was constructed with the help of Claude (Sonnet 4.6)idw_for_date <-function(fixes_date, stations_date, power =2) {# fixes_date: sf object for this date# stations_date: sf object with Temperature, RainDur for this date# power = 2 is the standard value# full distance matrix: n_fixes x n_stations dist_mat <-st_distance(fixes_date, stations_date) dist_mat <-matrix(as.numeric(dist_mat), nrow =nrow(fixes_date), ncol =nrow(stations_date))# avoid division by zero dist_mat[dist_mat ==0] <-1e-9 weights <-1/ (dist_mat^power) weights_norm <- weights /rowSums(weights) fixes_date |>mutate(Temperature_IDW =as.numeric(weights_norm %*% stations_date$Temperature),RainDur_IDW =apply(weights_norm, 1,function(w) { valid <-!is.na(stations_date$RainDur) # ignore stations with missing RainDurif(sum(valid) ==0) {return(NA) } w_valid <- w[valid] rain_valid <- stations_date$RainDur[valid]# renormalize weights only along valid stations w_valid <- w_valid /sum(w_valid)sum(w_valid * rain_valid) } ) )}# apply per date (groups are small: one per day x n_stations)unique_dates <-unique(google_timeline_sf_lv95$Date)result_list <-map(unique_dates, function(d) { fixes_date <- google_timeline_sf_lv95 |>filter(Date == d) stations_date <- weather_daily_df_wide |>filter(Date == d) |>inner_join(st_drop_geometry(weather_stations_sf) |>select(ID), by ="ID") |>left_join(weather_stations_sf |>select(ID, geometry), by ="ID") |>st_as_sf()if (nrow(stations_date) ==0) { fixes_date$Temperature_IDW <-NA fixes_date$RainDur_IDW <-NAreturn(fixes_date) }idw_for_date(fixes_date, stations_date)})google_timeline_weather_daily <-bind_rows(result_list)
Assign Weather Values
Code
# average weather values across whole hulldaily_weather_field <- google_timeline_weather_daily |>st_drop_geometry() |>group_by(Date) |>summarise(Temperature =mean(Temperature_IDW, na.rm =TRUE),RainDur =mean(RainDur_IDW, na.rm =TRUE) )daily_hulls_weather_field <- google_timeline_hulls |>left_join(daily_weather_field, by ="Date")
Weather Hourly (Field)
We will repeat this IDW process with the hourly weather data.
The measurements are taken by the same stations as for the daily weather data. Therefore, we only really need to adapt the input data. The concave hulls will stay the same, we will still take all fixes from one day into account to construct the movement space since our RQ IV asks about the temporal scale of the weather data.
Prepare Weather Data
Code
# prepare weather dataweather_hourly_df_wide <- weather_hourly_df |>filter(Parameter %in%c("T", "RainDur")) |># we only want T and RainDurselect(Datum, Standort, Parameter, Wert) |>pivot_wider( # we need the parameters as columnsnames_from = Parameter,values_from = Wert ) |>rename(Temperature = T,RainDur = RainDur, ID = Standort )
Code
google_timeline_sf_lv95 <- google_timeline_sf_lv95 |>mutate(Datum =floor_date(time, unit ="hour") )
Inverse Distance Weighting (IDW)
Code
# this code chunk was constructed with the help of Claude (Sonnet 4.6)idw_for_hour <-function(fixes_date, stations_date, power =2) {# fixes_date: sf object for this date# stations_date: sf object with Temperature, RainDur for this date# power = 2 is the standard value# full distance matrix: n_fixes x n_stations dist_mat <-st_distance(fixes_date, stations_date) dist_mat <-matrix(as.numeric(dist_mat), nrow =nrow(fixes_date), ncol =nrow(stations_date))# avoid division by zero dist_mat[dist_mat ==0] <-1e-9 weights <-1/ (dist_mat^power) weights_norm <- weights /rowSums(weights) fixes_date |>mutate(Temperature_IDW =as.numeric(weights_norm %*% stations_date$Temperature),RainDur_IDW =apply(weights_norm, 1,function(w) { valid <-!is.na(stations_date$RainDur) # ignore stations with missing RainDurif(sum(valid) ==0) {return(NA) } w_valid <- w[valid] rain_valid <- stations_date$RainDur[valid]# renormalize weights only along valid stations w_valid <- w_valid /sum(w_valid)sum(w_valid * rain_valid) } ) )}# apply per hour unique_hours <-unique(google_timeline_sf_lv95$Datum)result_list <-map(unique_hours, function(h) { fixes_hour <- google_timeline_sf_lv95 |>filter(Datum == h) stations_hour <- weather_hourly_df_wide |>filter(Datum == h) |>inner_join(st_drop_geometry(weather_stations_sf) |>select(ID), by ="ID") |>left_join(weather_stations_sf |>select(ID, geometry), by ="ID") |>st_as_sf()if (nrow(stations_hour) ==0) { fixes_hour$Temperature_IDW <-NA fixes_hour$RainDur_IDW <-NAreturn(fixes_hour) }idw_for_hour(fixes_hour, stations_hour)})google_timeline_weather_hourly <-bind_rows(result_list)
Assign Weather Values
Code
# average weather values across whole hull but still for the whole dayhourly_weather_field <- google_timeline_weather_hourly |>st_drop_geometry() |>group_by(Date) |>summarise(Temperature =mean(Temperature_IDW, na.rm =TRUE),RainDur =mean(RainDur_IDW, na.rm =TRUE) )hourly_hulls_weather_field <- google_timeline_hulls |>left_join(hourly_weather_field, by ="Date")
Weather 15 min (Field)
Now we want to go one step more into detail using the meteoblue weather data, which takes temperature measurements every 15 minutes at 93 stations across the whole city. It is important to note that meteoblue only measures temperature and not precipitation or rain duration.
Again, we will use the concave hulls per day, since we are interested in the effect of temporal granularity of weather data.
Prepare Weather Data
Code
# the meteoblue weather data is already in a wide format, therefore we do not need to change anything there.google_timeline_sf_lv95 <- google_timeline_sf_lv95 |>mutate(QuarterHour =floor_date(time, unit ="15 min") )
Inverse Distance Weighting (IDW)
Code
# If we would repeat the same code as earlier the runtime would increase significantly. Instead of hourly measurement of four stations we have 15 min measurements of 93 stations. That is why we adapted the code slightly. # we move the station preparation outside the loop --> so it does not get repeated all the timemeteoblue_weather_data_join <- meteoblue_weather_data |>left_join( meteoblue_stations |>select(locationID, EKoord, NKoord), by ="locationID") |>st_as_sf(coords =c("EKoord", "NKoord"),crs = crs_lv95 )
Code
# this code chunk was constructed with the help of Claude (Sonnet 4.6)idw_for_quarter_hour <-function(fixes_date, stations_date, power =2) {# fixes_date: sf object for this date# stations_date: sf object with Temperature for this date# power = 2 is the standard value# remove NA temperature stations stations_date <- stations_date |>filter(!is.na(value))# full distance matrix: n_fixes x n_stations dist_mat <-st_distance(fixes_date, stations_date) dist_mat <-matrix(as.numeric(dist_mat), nrow =nrow(fixes_date), ncol =nrow(stations_date))# avoid division by zero dist_mat[dist_mat ==0] <-1e-9 weights <-1/ (dist_mat^power) weights_norm <- weights /rowSums(weights) fixes_date |>mutate(Temperature_IDW =as.numeric(weights_norm %*% stations_date$value) )}# apply per quarter hourunique_quarter_hours <-unique(google_timeline_sf_lv95$QuarterHour)result_list <-map(unique_quarter_hours, function(q) { fixes_quarter_hour <- google_timeline_sf_lv95 |>filter(QuarterHour == q) stations_quarter_hour <- meteoblue_weather_data_join |>filter(timestamp == q)if (nrow(stations_quarter_hour) ==0) { fixes_quarter_hour$Temperature_IDW <-NAreturn(fixes_quarter_hour) }idw_for_quarter_hour(fixes_quarter_hour, stations_quarter_hour)})google_timeline_weather_quarter_hourly <-bind_rows(result_list)
Assign Weather Values
Code
# average weather values across whole hull but still for the whole dayquarter_hourly_weather_field <- google_timeline_weather_quarter_hourly |>st_drop_geometry() |>group_by(Date) |>summarise(Temperature =mean(Temperature_IDW, na.rm =TRUE) )quarter_hourly_hulls_weather_field <- google_timeline_hulls |>left_join(quarter_hourly_weather_field, by ="Date")
Code
# join datasets since there is no rain information in meteobluequarter_hourly_hulls_weather_field_join <- quarter_hourly_hulls_weather_field |>left_join( hourly_hulls_weather_field |>st_drop_geometry() |>select(RainDur, Date),by ="Date" )
Influence of Weather on Walking - ArcGIS Earth trajectories analysis
Now we switch from the timeline data to the ArcGIS Earth data, since Google timeline does not allow for precise movement parameter calculations due to the coarse sampling rate.
Segmentation of trajectories
First we split our trajectories into segments of stops and moves using a moving window. Since ArcGIS Earth has a very fine sampling rates (seconds) and we sometimes let the tracking run for a whole day, a lot of the fixes are stops and will not be needed for further analysis.
Following a similar procedure as in these studies (Guo et al. (2018))(Bonavita, Guidotti, and Nanni (2022)), we employed spatial and temporal tresholds to classify movement and non-movement. We classified the segment as “movement” if the distance travelled in 1 timestep was more than 5 meters within a time of 30 seconds. This allows for certain minimal movement, such as being in crowded pedestrian areas, without being automatically being classified as stop.
Code
# define function difftime_secs <-function(later, now){as.numeric(difftime(later, now, units ="secs"))}
Code
# define two timestamps for function using leadlater <-lead(tracks_lv95$timestamp)now <- tracks_lv95$timestamptracks_lv95$timelag <-difftime_secs(later, now)
Code
# sort after timestamptracks_lv95 <- tracks_lv95 |>arrange(timestamp)
Code
# compute distance and time differencetracks_lv95 <- tracks_lv95 |>mutate(step_dist =as.numeric(st_distance(geometry, lead(geometry), by_element =TRUE)),step_time =as.numeric(difftime(lead(timestamp), timestamp, units ="secs")) )
Code
# define threshold of moving window, movement if distance of 1 step more than 5 meters in a time of less than 30 secondstracks_lv95 <- tracks_lv95 |>mutate(moving_step = step_dist >5& step_time <30 )
Code
# assign new segment_id each time transition from stop to move or vice versa# to connect the still separate points into segments, with support of Chatgpttracks_lv95 <- tracks_lv95 |>mutate(change = moving_step !=lag(moving_step, default =first(moving_step)),segment_id =cumsum(change) )
Code
# calculate for each segment starting time, end time, duration, geometry, speedsegments <- tracks_lv95 |>group_by(segment_id, moving_step) |>summarise(start_time =first(timestamp),end_time =last(timestamp),duration =as.numeric(difftime(last(timestamp), first(timestamp), units ="secs") ),distance =sum(step_dist, na.rm =TRUE),speed =ifelse(duration >0, distance / duration, NA_real_),speed_kmh = speed *3.6,n_points =n(),geometry =st_union(geometry),.groups ="drop" )
Code
# classify segments to Movement or not (Stop)segments <- segments |>mutate(type =ifelse(moving_step, "MOVE", "STOP"))
# trim upper tail, with support of ChatGPTupper <-quantile(segments$speed_kmh, 0.975, na.rm =TRUE)segments_trim <- segments |>filter(speed_kmh <= upper)
As can be seen in the plots below, the majority of the fixes have a speed below 20 km/h, indicating high shares of foot travel or slow transport.
# boxplot for distributionboxplot( segments_trim$speed_kmh,main ="Distribution of Speed (km/h) – trimmed top 2.5%",ylab ="Speed (km/h)",col ="lightgreen")
Code
# histogram for distributionhist( segments_trim$speed_kmh,main ="Distribution of Speed (km/h) – trimmed top 2.5%",ylab ="Speed (km/h)",col ="lightgreen")
Travel Mode Detection - Walking
Code
# In transportation planning, free flow pedestrian walking speed is assumed to be between 0.8 m/s and 1.5 m/s or 3 to 7 km/h. In our case, slower walking speeds could be possible since the city of Zurich can be rather densely packed and free flow speed might not be achieved. This is why we extended the lower threshold. # create only walking segments (speed between 1 and 7 km/h)walking_segments <- segments |>filter(segments$speed_kmh <7& segments$speed_kmh >1 )
Construct Voronoi Polygons around meteoblue Stations
Code
# To assign each segment the nearest meteoblue measuring station, we construct Voronoi polygons, following the same approach as before.# create geometry out of coordinates for meteoblue_stationsmeteoblue_sf <- meteoblue_stations |>st_as_sf(coords=c("lonDecimal", "latDecimal"), crs = crs_wgs84)meteoblue_sf <-st_transform(meteoblue_sf, crs = crs_lv95)
Code
# create union of all station points and set city extentpts_union <-st_union(st_geometry(meteoblue_sf))envelope_zurich <-st_as_sfc(st_bbox(stadtgrenze))
Code
# create Voronoi tesselation and extract polygons out of it, with support of ChatGPTvoronoi_meteoblue <-st_voronoi(pts_union, envelope=envelope_zurich)voronoi_polygons <-st_collection_extract( voronoi_meteoblue,"POLYGON")# convert to sf objectvoronoi_sf <-st_sf(geometry =st_cast(voronoi_polygons, "POLYGON"))
Code
# assign station attributes to polygonsvoronoi_sf$locationID <- meteoblue_sf$locationID# clip to Zurich city boundaryvoronoi_clipped_meteoblue <-st_intersection( voronoi_sf, stadtgrenze)
Code
# join between voronoi stations and meteoblue weather data voronoi_weather <- voronoi_clipped_meteoblue |>left_join( meteoblue_weather_data,by ="locationID" )
Code
# visualisationggplot() +geom_sf(data = voronoi_clipped_meteoblue, aes(fill = locationID)) +geom_sf(data = stadtgrenze, fill =NA, color ="black") +geom_sf(data = meteoblue_sf, color ="red") +coord_sf(crs = crs_lv95) +scale_fill_viridis_d(option ="turbo") +guides(fill ="none") +labs(title ="Voronoi Polygons around meteoblue Stations in Zurich",fill ="Station",x ="Easting [m]",y ="Northing [m]" ) +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8),axis.text =element_blank(),axis.title =element_blank() )
Assign Segments to Voronoi Polygons
First, we assign each segment to voronoi polygon using a spatial join. Second, we also apply a temporal join to link the weather data of the same date and time as the trajectory. For that, we define a “representative time” for each segment, meaning the midtime (end_time-start_time)/2. The time difference between each segment and the weather data is computed and the weather entry with the lowest time difference is then selected for matching. Added to this, a threshold of 120 minutes is set, where time differences above it, are automatically excluded for a temporal join. Regarding the validation of the temporal join, the median time difference of 3.84 min indicates very high temporal linkage.
Code
# only look at point instead of segmentsegment_points <- walking_segments |>st_cast("POINT")# join with intersectsegment_points_weather <-st_join( segment_points, voronoi_clipped_meteoblue["locationID"],join = st_intersects)# aggregate backmeteoblue_segments <- segment_points_weather |>group_by(segment_id, locationID) |>summarise(geometry =st_union(geometry))# clip to extent of Zurichmeteoblue_segments_clipped <-st_intersection( meteoblue_segments, stadtgrenze)
Code
# visualize Movement Segments with meteoblue weather station polygonsggplot() +geom_sf(data = voronoi_clipped_meteoblue, aes(fill = locationID), alpha =0.3) +geom_sf(data = stadtgrenze, fill =NA, color ="black") +geom_sf(data = meteoblue_segments_clipped, color ="blue", size =1) +coord_sf(crs = crs_lv95) +scale_fill_viridis_d(option ="turbo") +guides(fill ="none") +labs(title ="Movement Segments with meteoblue weather station polygons",x ="Easting",y ="Northing" ) +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8),axis.text =element_blank(),axis.title =element_blank() )
Temporal Join of Weather Data and Walking Segments
Code
# find most representative time (midtime) of each segmentsegments <- segments |>mutate(mid_time = start_time + (end_time - start_time) /2 )
Code
# set as posix datetime objectmeteoblue_weather_data <- meteoblue_weather_data |>mutate(timestamp =as.POSIXct(timestamp))
Code
# add spatial information to segmentssegments_spatial <-st_join( segments, meteoblue_segments["locationID"],join = st_intersects)
To analzye the effect of precipitation on walking speed, we opted for two different spatial and temporal scales of the weather data (small cross-scale analysis (Laube and Purves (2011))). First, conducted an analysis with weather data on a city-level. Second, we applied the same procedure, but with weather data on a station-level.
# determine representative time for segmentwalking_segments <- walking_segments |>mutate(mid_time = start_time + (end_time - start_time) /2 )
A) City-level Analysis
Code
# We decided on using the hourly weather data since it resembles reality closer than daily weather data. # timestamp set as datetime objectrain_city_hourly <- weather_hourly_df |>filter(Parameter =="RainDur") |>mutate(timestamp =as.POSIXct(Datum, tz ="Europe/Zurich")) |>group_by(timestamp) |>summarise(rain =mean(Wert, na.rm =TRUE), .groups ="drop")
Code
# temporal join, merge all segments with all weather data and keep closestanalysis_city <- walking_segments |>st_drop_geometry() |>mutate(mid_time =as.POSIXct(mid_time, tz ="Europe/Zurich")) |>crossing(rain_city_hourly) |>mutate(time_diff =abs(as.numeric(difftime(mid_time, timestamp, units ="mins")))) |>group_by(segment_id) |>slice_min(time_diff, n =1) |>ungroup() |>filter(!is.na(rain))
B) Station-level Analysis
Code
# join segment with nearest station firstsegments_station <-st_join( walking_segments, voronoi_clipped["ID"],join = st_nearest_feature)
# group if rain or not and analyze speed respectivelyanalysis_data |>group_by(rain_flag) |>summarise(mean_speed =mean(speed_kmh, na.rm =TRUE),median_speed =median(speed_kmh, na.rm =TRUE),sd_speed =sd(speed_kmh, na.rm =TRUE),n =n() )
In this chapter we will present the performed statistical tests as well as their results and implications. Thanks to the different weather datasets and our own tracking data, coming from two different sources, we were able to achieve results that can later be used to discuss our research questions.
Statistical Analysis Movement Space vs. Weather
After preparing the data and creating all necessary auxiliary elements we are now finally able to test our hypothesis.
# visualise the variable Temperatureggplot(daily_hulls_weather, aes(x = Temperature, y =log(area_m2))) +geom_point(size =1.2) +geom_smooth(method ="lm", se =TRUE, linewidth =1) +labs(x ="Temperature [°C]",y ="log(Activity Space Area [m^2])",title ="Relationship Between Temperature and Activity Space Size",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
Code
# visualise the variable RainDurggplot(daily_hulls_weather, aes(x = RainDur, y =log(area_m2))) +geom_point(size =1.2) +geom_smooth(method ="lm", se =TRUE, linewidth =1) +labs(x ="Rain Duration [min]",y ="log(Activity Space Area [m^2])",title ="Relationship Between Rain Duration and Activity Space Size",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
We can visually assess that there is a positive relation between temperature and activity space and a negative relation between temperature and rain duration.
Code
# linear model with log(area)lm1 <-lm(log(area_m2) ~ Temperature + RainDur, data = daily_hulls_weather)
Code
# full model summary (R^2, adjusted R^2, p-values, residual std error)summary(lm1)
Call:
lm(formula = log(area_m2) ~ Temperature + RainDur, data = daily_hulls_weather)
Residuals:
Min 1Q Median 3Q Max
-4.6620 -0.5310 -0.0180 0.8015 1.9240
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 13.0708461 0.6248266 20.919 <2e-16 ***
Temperature 0.0888042 0.0532322 1.668 0.104
RainDur -0.0004278 0.0014005 -0.305 0.762
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1.278 on 37 degrees of freedom
(3 Beobachtungen als fehlend gelöscht)
Multiple R-squared: 0.09951, Adjusted R-squared: 0.05084
F-statistic: 2.044 on 2 and 37 DF, p-value: 0.1438
Code
# delete comments if one wants to inspect the plots closer#par(mfrow = c(1, 1))#plot(lm1, which = 1) # Residuals vs Fitted#plot(lm1, which = 2) # Q-Q plot#plot(lm1, which = 3) # Scale-Location#plot(lm1, which = 5) # Residuals vs Leverage
This first model is not statistically significant as a whole (F-statistic p = 0.1438). Meaning Temperature and RainDur together do explain some variation in activity space but the low R^2 (0.1) and adjusted R^2 (0.05) suggests other factors (e.g. day of week, work/leisure day, season) that influence activity space more than weather.
There is a suggestive positive trend with Temperature, but we cannot confidently reject H0.
# visualise the variable Temperatureggplot(daily_hulls_weather_field, aes(x = Temperature, y =log(area_m2))) +geom_point(size =1.2) +geom_smooth(method ="lm", se =TRUE, linewidth =1) +labs(x ="Temperature [°C]",y ="log(Activity Space Area [m^2])",title ="Relationship Between Temperature and Activity Space Size",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
Code
# visualise the variable RainDurggplot(daily_hulls_weather_field, aes(x = RainDur, y =log(area_m2))) +geom_point(size =1.2) +geom_smooth(method ="lm", se =TRUE, linewidth =1) +labs(x ="Rain Duration [min]",y ="log(Activity Space Area [m^2])",title ="Relationship Between Rain Duration and Activity Space Size",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
We see the same pattern with our new field (IDW) approach.
Code
# linear model with log(area)lm2 <-lm(log(area_m2) ~ Temperature + RainDur, data = daily_hulls_weather_field)
Code
# full model summary (R^2, adjusted R^2, p-values, residual std error)summary(lm2)
Call:
lm(formula = log(area_m2) ~ Temperature + RainDur, data = daily_hulls_weather_field)
Residuals:
Min 1Q Median 3Q Max
-4.5100 -0.5640 0.0028 0.7882 1.9362
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 13.2111085 0.6711081 19.686 <2e-16 ***
Temperature 0.0733416 0.0548604 1.337 0.190
RainDur -0.0008364 0.0012219 -0.684 0.498
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1.272 on 36 degrees of freedom
(4 Beobachtungen als fehlend gelöscht)
Multiple R-squared: 0.1021, Adjusted R-squared: 0.05226
F-statistic: 2.048 on 2 and 36 DF, p-value: 0.1438
The P-value of the RainDur is quite lower than from lm1, indicating that the interpolation of precipitation duration is a more appropriate way of identifying a relationship between weather and movement.
Furthermore, the R^2 and Adjusted R^2 values are slightly higher, meaning that our interpolated model explains a bit more of the values than the model with the Voronoi polygons.
# visualise the variable Temperatureggplot(hourly_hulls_weather_field, aes(x = Temperature, y =log(area_m2))) +geom_point(size =1.2) +geom_smooth(method ="lm", se =TRUE, linewidth =1) +labs(x ="Temperature [°C]",y ="log(Activity Space Area [m^2])",title ="Relationship Between Temperature and Activity Space Size",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
Code
# visualise the variable RainDurggplot(hourly_hulls_weather_field, aes(x = RainDur, y =log(area_m2))) +geom_point(size =1.2) +geom_smooth(method ="lm", se =TRUE, linewidth =1) +labs(x ="Rain Duration [min]",y ="log(Activity Space Area [m^2])",title ="Relationship Between Rain Duration and Activity Space Size",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
Again, we see the same visual pattern now with the hourly data.
Code
# linear model with log(area)lm3 <-lm(log(area_m2) ~ Temperature + RainDur, data = hourly_hulls_weather_field)
Code
# full model summary (R^2, adjusted R^2, p-values, residual std error)summary(lm3)
Call:
lm(formula = log(area_m2) ~ Temperature + RainDur, data = hourly_hulls_weather_field)
Residuals:
Min 1Q Median 3Q Max
-4.6746 -0.5891 -0.1289 0.9238 1.9224
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 13.69650 0.61238 22.366 <2e-16 ***
Temperature 0.02802 0.04826 0.581 0.5647
RainDur -0.08160 0.03394 -2.404 0.0209 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1.243 on 40 degrees of freedom
Multiple R-squared: 0.1893, Adjusted R-squared: 0.1488
F-statistic: 4.67 on 2 and 40 DF, p-value: 0.01504
We see a significant negative effect of RainDur on the movement area. The whole model explains much more of the variance since the Adjusted R^2 value is 0.15, meaning that 15% of the variance is explained with our model. Still, this is not a majority but a better model than the one with daily weather data.
If we have a look at for example the 24.03.2026 we can see the differences between aggregated daily and hourly weather data. The daily one says that on that date it was 8.1°C with a RainDur of 187 min, which is quite a lot. The hourly one says that it was 10.7°C with no rain. This means that while over the whole day, it rained quite a lot, we were only moving while it was not raining and slightly warmer, effectively being a great example for our research questions.
D) 15 min - Field
Code
# visualise the variable Temperatureggplot(quarter_hourly_hulls_weather_field, aes(x = Temperature, y =log(area_m2))) +geom_point(size =1.2) +geom_smooth(method ="lm", se =TRUE, linewidth =1) +labs(x ="Temperature [°C]",y ="log(Activity Space Area [m^2])",title ="Relationship Between Temperature and Activity Space Size",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
Note: Here we only have 15 min temperature values and not 15 min rain values. This is why we earlier merged rain duration from hourly data into quarter-hourly temperature data.
Code
# linear model with log(area)lm4 <-lm(log(area_m2) ~ Temperature + RainDur, data = quarter_hourly_hulls_weather_field_join)
Code
# full model summary (R^2, adjusted R^2, p-values, residual std error)summary(lm4)
Call:
lm(formula = log(area_m2) ~ Temperature + RainDur, data = quarter_hourly_hulls_weather_field_join)
Residuals:
Min 1Q Median 3Q Max
-4.6513 -0.5815 -0.1751 0.9693 1.9765
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 13.78406 0.58028 23.754 <2e-16 ***
Temperature 0.02200 0.04821 0.456 0.6505
RainDur -0.08392 0.03360 -2.498 0.0167 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1.245 on 40 degrees of freedom
Multiple R-squared: 0.1867, Adjusted R-squared: 0.146
F-statistic: 4.591 on 2 and 40 DF, p-value: 0.01603
This last model is the best we have. It shows that hourly RainDur has a statistically significant effect (p-value = 0.0167) and temperature has its lowest p-value across all models, indicating a more reliable measurement with the 15 min interval. Our model explains 15% of the variation (Adjusted R^2) and with an F-value of 4.6 and a p-value for the whole model of 0.016, this means that the model as a whole is significant. Thsi in turn means, that temperature (15 min measurements) and rain duration (hourly measurements) have an impact on our daily activity space.
Statistical Analysis - Influence of Weather on Walking
Influence on Walking Speed
Code
# visualizeggplot(segments_weather_clean, aes(x = value, y = speed_kmh)) +geom_point(alpha =0.3) +geom_smooth(method ="lm", se =TRUE, linewidth =1) +coord_cartesian(ylim =c(0, 10)) +labs(x ="Temperature [°C]",y ="Speed [km/h]",title ="Relationship Between Temperature and Walking Speed",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
Code
# trend analysistrend <-lm(speed_kmh ~ value, data = segments_weather_clean)summary(trend)
Call:
lm(formula = speed_kmh ~ value, data = segments_weather_clean)
Residuals:
Min 1Q Median 3Q Max
-3.0406 -1.0729 0.2953 1.2193 2.6072
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 4.253933 0.161892 26.276 <2e-16 ***
value -0.005095 0.014407 -0.354 0.724
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1.513 on 272 degrees of freedom
(5 Beobachtungen als fehlend gelöscht)
Multiple R-squared: 0.0004597, Adjusted R-squared: -0.003215
F-statistic: 0.1251 on 1 and 272 DF, p-value: 0.7238
There is no relationship between walking speed and temperature when taking the whole dataset. This was to be expected, as shown in Introduction.
Code
# classification of different temperature groupssegments_weather_clean <- segments_weather_clean |>mutate(temp_group =cut(value,breaks =quantile(value, probs =seq(0, 1, 0.25), na.rm =TRUE),include.lowest =TRUE) )lm(speed_kmh ~ temp_group, data = segments_weather_clean)
However, if we classify the segments into different temperature groups, a difference between the groups is visible. In general, higher temperatures are associated with lower walking speed, from 4.8km/h for cool temperatures to almost 4km/h for warm temperatures, except for very cold temperatures, where also slow walking speeds are identified.
The median walking speed varies modestly across temperature groups (4.1–4.8 km/h), but due to the large within-group spread this difference is not statistically significant (p-value = 0.24).
A) City-level Analysis
Based on the city-level weather data, we clearly see that with increasing rain duration, walking speed is reduced.
Code
# visualizeggplot(analysis_city, aes(rain, speed_kmh)) +geom_point(alpha =0.3) +geom_smooth(method ="lm") +labs(x ="Rain Duration [min]",y ="Speed [km/h]",title ="City-level Relationship Between Rain and Walking Speed",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
B) Station-level Analysis
Similarly to the city-level data, a negative trend is visible. However, with station-level weather data this trend is more pronounced, where with high precipitation duration walking speeds are below 3km/h.
Code
# visualizeggplot(analysis_station, aes(Wert, speed_kmh)) +geom_point(alpha =0.3) +geom_smooth(method ="lm") +labs(x ="Rain Duration [min]",y ="Speed [km/h]",title ="Station-level Relationship Between Rain and Walking Speed",subtitle ="Linear regression with 95% confidence interval") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
The relationship between speed and rain is also tested using a wilcox test. With a p-value of 0.2229, no significant relationship can be found.
Code
# test if significant relationshipwilcox.test(speed_kmh ~ rain_flag, data = analysis_data)
Wilcoxon rank sum test with continuity correction
data: speed_kmh by rain_flag
W = 2016, p-value = 0.2229
alternative hypothesis: true location shift is not equal to 0
Code
# see if relationshiplm(speed_kmh ~ Wert, data = analysis_data)
If we only include actual raining values (>0) for the fitting line, the relationship becomes even more visible.
Code
# visualize, but only values where there actually is rain (>0)ggplot(analysis_data, aes(Wert, speed_kmh)) +geom_jitter(alpha =0.2, height =0) +geom_smooth(data =filter(analysis_data, Wert >0),method ="lm",color ="blue") +labs(x ="Rain Duration [min]",y ="Speed [km/h]",title ="Relationship Between Rain and Walking Speed only for observations with rainfall > 0",subtitle ="Linear regression with 95% confidence interval ") +theme_minimal() +theme(plot.title =element_text(size =14, face ="bold", hjust =0.5),legend.title =element_text(size =10),legend.text =element_text(size =8) )
Discussion
RQ1: How does temperature affect walking speed?
On the first sight, it seems that there is no relationship between temperature and walking speed. However, when dividing the segments into temperature groups, clear differences become visible. In general, higher temperatures are associated with lower walking speed, from 4.8km/h for cool temperatures to almost 4km/h for warm temperatures except for very cold temperatures, where also slow walking speeds are identified. Since the coldest temperatures are close to zero, a possible explanation for the low speed with cold temperatures is the increased caution and thus reduced velocity due to an elevated danger of slipping. These patterns are in line with previous research, which highlighted a certain increase in walking with higher temperatures, while very cold conditions could lead to slower speeds (Montigny, Ling, and Zacharias (2012); Giannoulaki and Christoforou (2024)), as type of u-shaped curve (Yerkes and Dodson (1908)). However, the relationship did not prove to be significant, which is likely due to the small set of trajectories.
RQ2: How does precipitation affect walking speed?
Contrary to our expectations, increasing precipitation duration is associated with lower walking velocity. We hypothesized similarly to Giannoulaki and Christoforou (2024) that pedestrians would increase their speed under unfavourable weather to minimize exposure. However, other studies argue that precipitation reduces similarly to snow pavement quality and thus implies slower movement patterns. (Montigny, Ling, and Zacharias (2012); Giannoulaki and Christoforou (2024)). Overall, due to the very small amount of rain data caused by dry months and two stations not collecting any rain data, this part of the research question can not be adequately answered.
RQ3: How do temperature and precipitation affect the spatial extent of activity spaces?
Using our last model, lm4, with the 15 min temperature measurements and hourly precipitation measurements, these two factors influence the spatial extent of activity spaces, although their effects are relatively small. Temperature shows a consistent positive relationship with activity space size, but its effect is only clearly observed when measured at shorter temporal intervals. Precipitation has a stronger negative influence, with longer rain durations reducing activity space extent, which was shown to be statistically significant. Overall, precipitation appears to be the stronger limiting factor, while temperature contributes to explaining variations.
RQ4: Do different temporal and spatial scales of the weather data matter?
A) Temporal
The results demonstrate that weather data resolution is crucial, as hourly and 15 min measurements better capture actual movement conditions than daily averages. The explanatory power of the models increases as the temporal resolution becomes finer (daily –> hourly –> 15 min). Daily aggregated weather data shows weak and mostly non-significant relationships, suggesting that coarse temporal averages fail to capture the actual weather conditions experienced during movement.
Hourly data improved our model performances and revealed a statistically significant negative effect of precipitation. The strongest results are obtained at the 15 min scale (lm4). This model achieves the highest explanatory power and statistical significance which confirms our hypothesis that finer temporal scale improves the detection of weather effects.
B) Spatial
The comparison between the analysis with city-level and with station-level weather data, revealed that it can have an effect on the quantification of the relationship. Although in both cases a negative relationship between precipitation and walking speed was identified, the station-level analysis showed a more clear trend. This can be likely explained by closer matching of weather stations to a certain segment.
Limitations
This analysis has various limitations. First, the classification of segments with static thresholds into movement vs. non-movement is frequent, but recent research has shown that the selection of static thresholds is often not adequate and makes no distinction between different use cases. The proposed alternative would be a more flexible and user-adapted approach, which however is out of scope for this project (Bonavita, Guidotti, and Nanni (2022)).
Second, the low amount of trajectory and weather data means that our findings need to be interpreted with caution. This is especially the case for the analysis with precipitation data, as very few entries were available due to unusual dry conditions. Thus, the conducted tests and regression analysis have very limited power.
Third, important behavioural and environmental factors were not included. Trip purpose is likely a major determinant of mobility patterns, as walking behaviour is strongly driven by daily routines and situational needs (Montigny, Ling, and Zacharias (2012)). Additional influences such as sunlight, storms, weather complexity and urban context may also affect walking speed and behaviour (Giannoulaki and Christoforou (2024); Rotton, Shats, and Standers (1990); Shuichi et al. (2021)). Moreover, precipitation effects may vary over time, including delayed or anticipatory responses (Zhao et al. (2019)).
Fourth, data limitations remain in the movement representation itself, as noted in Methods (SDE). Additionally, concave hulls were calculated at daily scale only, even when weather data was available at finer resolutions, but this was a necessary simplification to maintain comparability across analyses. Also, rain duration does not capture rain intensity, which arguably might be even more important. Finally, we introduced a bias when we filtered our movements to the city of Zurich because when the weather is nice, we might have even moved out of the canton, increasing our movement space.
AI Statement
LLMs were used to improve the structure of sentences and grammar as well as creating and debugging code, especially for content and challenges that go beyond the scope of the course. All content, analysis, and arguments remain the author’s own and any information derived from external sources is properly cited.
Appendix
Wordcount
Disclaimer: To improve structure and clarity, We included headings and subheadings for the whole report, which are around 1800 characters. That is why the total number of characters is slightly over the maximum number of characters.
Code
wordcountaddin::word_count("index.qmd") # counts words
Bonavita, Andrea, Riccardo Guidotti, and Mirco Nanni. 2022. “Individual and Collective Stop-Based Adaptive Trajectory Segmentation.”Geoinformatica 26 (3): 451–77. https://doi.org/10.1007/s10707-021-00449-8.
Giannoulaki, Maria, and Zoi Christoforou. 2024. “Pedestrian Walking Speed Analysis: A Systematic Review.”Sustainability 16 (11): 4813.
Guo, Sini, Xiang Li, Wai-Ki Ching, Ralescu Dan, Wai-Keung Li, and Zhiwen Zhang. 2018. “GPS Trajectory Data Segmentation Based on Probabilistic Logic.”International Journal of Approximate Reasoning 103: 227–47.
Laube, Patrick. 2009. “Progress in Movement Pattern Analysis.” In Behaviour Monitoring and Interpretation - BMI - Smart Environments, edited by Björn Gottfried and Hamid Aghajan, 43–71. Amsterdam, The Netherlands: IOS Press.
Laube, Patrick, and Ross S Purves. 2011. “How Fast Is a Cow? Cross-Scale Analysis of Movement Data.”Transactions in GIS 15 (3): 401–18.
Montigny, Luc de, Richard Ling, and John Zacharias. 2012. “The Effects of Weather on Walking Rates in Nine Cities.”Environment and Behavior 44 (November): 821–40. https://doi.org/10.1177/0013916511409033.
Nathan, Ran, Wayne M Getz, Eloy Revilla, Marcel Holyoak, Ronen Kadmon, David Saltz, and Peter E Smouse. 2008. “A Movement Ecology Paradigm for Unifying Organismal Movement Research.”Proceedings of the National Academy of Sciences 105 (49): 19052–59.
Rotton, James, Mark Shats, and Robert Standers. 1990. “Temperature and Pedestrian Tempo: Walking Without Awareness.”Environment and Behavior 22 (5): 650–74.
Shuichi, Obuchi, Hisashi Kawai, Juan Garbalosa, Kazumasa Nishida, and Kenji Murakawa. 2021. “Walking Is Regulated by Environmental Temperature.”Scientific Reports 11 (June). https://doi.org/10.1038/s41598-021-91633-1.
Weibel, Robert. 2024. “Point Patterns - Polygon Delineation.” University of Zurich; Lecture slides, GEO 872 Advanced Spatial Analysis I.
Yerkes, Robert M., and John D. Dodson. 1908. “The Relation of Strength of Stimulus to Rapidity of Habit-Formation.”Journal of Comparative Neurology and Psychology 18 (5): 459–82. https://doi.org/https://doi-org.ezproxy.uzh.ch/10.1002/cne.920180503.
Zhao, Jinbao, Cong Guo, Ruhua Zhang, Dong Guo, and Mathew Palmer. 2019. “Impacts of Weather on Cycling and Walking on Twin Trails in Seattle.”Transportation Research Part D: Transport and Environment 77: 573–88.