Welton Beck MoRPH analysis

Author

Lydia Larocque

Published

August 26, 2026

1. Setup

# load packages
library(tidyverse)
library(janitor)
library(flextable)
library(patchwork)
library(effectsize)

set.seed(42)

# create outputs folder
dir.create("outputs", showWarnings = TRUE)
dir.create("outputs/figures", showWarnings = TRUE)
dir.create("outputs/tables", showWarnings = TRUE)

2. Data preparation and cleaning

# import data 
raw_data <- read_csv("raw_data.csv")

# check data is present with no missing values
names(raw_data)
 [1] "...1" "CR1"  "CR2"  "CR3"  "CR4"  "CR5"  "IR1"  "IR2"  "IR3"  "IR4" 
[11] "IR5" 
view(raw_data)

# restructure MoRPH data into a tidy format
morph_data <- raw_data %>%
  rename(index = `...1`) %>%
  pivot_longer(cols = CR1:IR5,
               names_to = "module",
               values_to = "value")
morph_data
# A tibble: 140 × 3
   index                          module value
   <chr>                          <chr>  <chr>
 1 INDEX 1 - Number of flow types CR1    1    
 2 INDEX 1 - Number of flow types CR2    1    
 3 INDEX 1 - Number of flow types CR3    1    
 4 INDEX 1 - Number of flow types CR4    1    
 5 INDEX 1 - Number of flow types CR5    1    
 6 INDEX 1 - Number of flow types IR1    2    
 7 INDEX 1 - Number of flow types IR2    2    
 8 INDEX 1 - Number of flow types IR3    2    
 9 INDEX 1 - Number of flow types IR4    2    
10 INDEX 1 - Number of flow types IR5    1    
# ℹ 130 more rows
# include the riparian condition and add numbers 1-5 for individual modules
morph_data <- morph_data %>%
  mutate(condition = if_else(
    str_starts(module, "CR"),
    "Tree-lined",
    "Non-tree-lined"),
    module_position = as.numeric(str_extract(module, "\\d+")))
morph_data
# A tibble: 140 × 5
   index                          module value condition      module_position
   <chr>                          <chr>  <chr> <chr>                    <dbl>
 1 INDEX 1 - Number of flow types CR1    1     Tree-lined                   1
 2 INDEX 1 - Number of flow types CR2    1     Tree-lined                   2
 3 INDEX 1 - Number of flow types CR3    1     Tree-lined                   3
 4 INDEX 1 - Number of flow types CR4    1     Tree-lined                   4
 5 INDEX 1 - Number of flow types CR5    1     Tree-lined                   5
 6 INDEX 1 - Number of flow types IR1    2     Non-tree-lined               1
 7 INDEX 1 - Number of flow types IR2    2     Non-tree-lined               2
 8 INDEX 1 - Number of flow types IR3    2     Non-tree-lined               3
 9 INDEX 1 - Number of flow types IR4    2     Non-tree-lined               4
10 INDEX 1 - Number of flow types IR5    1     Non-tree-lined               5
# ℹ 130 more rows
# Isolating the indices to be analysed further
morph_index <- morph_data %>%
  filter(index %in% c(
    "INDEX 1 - Number of flow types",
    "INDEX 3 - Number of bed material types",
    "INDEX 7 - Extent of superficial bed siltation",
    "INDEX 8 - Channel physical habitat complexity",
    "INDEX 9 - Number of aquatic vegetation morphotypes",
    "INDEX 10 - Riparian physical habitat complexity",
    "INDEX 11 - Riparian vegetation structural complexity",
    "INDEX 12 - Human pressure imposed by bank top land cover"))
view(morph_index)
unique(morph_index$index)
[1] "INDEX 1 - Number of flow types"                          
[2] "INDEX 3 - Number of bed material types"                  
[3] "INDEX 7 - Extent of superficial bed siltation"           
[4] "INDEX 8 - Channel physical habitat complexity"           
[5] "INDEX 9 - Number of aquatic vegetation morphotypes"      
[6] "INDEX 10 - Riparian physical habitat complexity"         
[7] "INDEX 11 - Riparian vegetation structural complexity"    
[8] "INDEX 12 - Human pressure imposed by bank top land cover"
# checking values recorded for each index
morph_index %>%
  group_by(index) %>%
  summarise(values = paste(unique(value), collapse = ", "))
# A tibble: 8 × 2
  index                                                    values               
  <chr>                                                    <chr>                
1 INDEX 1 - Number of flow types                           1, 2                 
2 INDEX 10 - Riparian physical habitat complexity          3, 4.071428571, 3.75…
3 INDEX 11 - Riparian vegetation structural complexity     4, 5, 5.5, 7, 3.5, 4…
4 INDEX 12 - Human pressure imposed by bank top land cover 3.5, 5               
5 INDEX 3 - Number of bed material types                   2, 1, 3, 4           
6 INDEX 7 - Extent of superficial bed siltation            7.65, 6.8, 6.7, 3.55…
7 INDEX 8 - Channel physical habitat complexity            2.083333333, 1.25, 2…
8 INDEX 9 - Number of aquatic vegetation morphotypes       1, 2, 3, 4           
# shortening index names
morph_plot_data <- morph_index %>%
  mutate(metric = case_when(
    index == "INDEX 1 - Number of flow types" ~ "Number of flow types",
    index == "INDEX 3 - Number of bed material types" ~ "Number of bed material types",
    index == "INDEX 7 - Extent of superficial bed siltation" ~ "Bed siltation",
    index == "INDEX 8 - Channel physical habitat complexity" ~ "Channel habitat complexity",
    index == "INDEX 9 - Number of aquatic vegetation morphotypes" ~ "Aquatic vegetation morphotypes",
    index == "INDEX 10 - Riparian physical habitat complexity" ~ "Riparian habitat complexity",
    index == "INDEX 11 - Riparian vegetation structural complexity" ~ "Riparian vegetation complexity",
    index == "INDEX 12 - Human pressure imposed by bank top land cover" ~ "Bank-top human pressure" ))
view(morph_plot_data)

# checking the data
table(morph_plot_data$metric, useNA = "ifany")

Aquatic vegetation morphotypes        Bank-top human pressure 
                            10                             10 
                 Bed siltation     Channel habitat complexity 
                            10                             10 
  Number of bed material types           Number of flow types 
                            10                             10 
   Riparian habitat complexity Riparian vegetation complexity 
                            10                             10 
morph_plot_data %>% select(metric, condition, module_position, value)
# A tibble: 80 × 4
   metric               condition      module_position value
   <chr>                <chr>                    <dbl> <chr>
 1 Number of flow types Tree-lined                   1 1    
 2 Number of flow types Tree-lined                   2 1    
 3 Number of flow types Tree-lined                   3 1    
 4 Number of flow types Tree-lined                   4 1    
 5 Number of flow types Tree-lined                   5 1    
 6 Number of flow types Non-tree-lined               1 2    
 7 Number of flow types Non-tree-lined               2 2    
 8 Number of flow types Non-tree-lined               3 2    
 9 Number of flow types Non-tree-lined               4 2    
10 Number of flow types Non-tree-lined               5 1    
# ℹ 70 more rows
# calculating integrated physical habitat complexity by combining MoRPH indices 
# 8 and 10
morph_physical <- morph_plot_data %>%
  filter(index %in% c("INDEX 8 - Channel physical habitat complexity",
                      "INDEX 10 - Riparian physical habitat complexity")) %>%
  mutate(value = as.numeric(value)) %>%
  group_by(condition, module_position) %>%
  summarise(integrated_complexity = sum(value),
            .groups = "drop")
morph_physical
# A tibble: 10 × 3
   condition      module_position integrated_complexity
   <chr>                    <dbl>                 <dbl>
 1 Non-tree-lined               1                  5.58
 2 Non-tree-lined               2                  6   
 3 Non-tree-lined               3                  5.33
 4 Non-tree-lined               4                  5.58
 5 Non-tree-lined               5                  6.83
 6 Tree-lined                   1                  5.08
 7 Tree-lined                   2                  5.32
 8 Tree-lined                   3                  5.83
 9 Tree-lined                   4                  6.42
10 Tree-lined                   5                  6.5 
# mean for each riparian condition
morph_physical_summary <- morph_physical %>%
  group_by(condition) %>%
  summarise(mean = mean(integrated_complexity),
            sd = sd(integrated_complexity),
            min = min(integrated_complexity),
            max = max(integrated_complexity))
morph_physical_summary
# A tibble: 2 × 5
  condition       mean    sd   min   max
  <chr>          <dbl> <dbl> <dbl> <dbl>
1 Non-tree-lined  5.87 0.591  5.33  6.83
2 Tree-lined      5.83 0.634  5.08  6.5 
# calculating descriptive statistics for each MoRPH index and riparian condition
morph_summary <- morph_plot_data %>%
  group_by(metric, condition) %>%
  summarise(Mean = mean(value, na.rm = TRUE),
            SD = sd(value, na.rm = TRUE),
            Minimum = min(value, na.rm = TRUE),
            Maximum = max(value, na.rm = TRUE),
            .groups = "drop")
morph_summary
# A tibble: 16 × 6
   metric                         condition       Mean    SD Minimum     Maximum
   <chr>                          <chr>          <dbl> <dbl> <chr>       <chr>  
 1 Aquatic vegetation morphotypes Non-tree-lined    NA 0.837 2           4      
 2 Aquatic vegetation morphotypes Tree-lined        NA 0.548 1           2      
 3 Bank-top human pressure        Non-tree-lined    NA 0.822 3.5         5      
 4 Bank-top human pressure        Tree-lined        NA 0     3.5         3.5    
 5 Bed siltation                  Non-tree-lined    NA 1.22  0.1         3.35   
 6 Bed siltation                  Tree-lined        NA 1.58  3.55        7.65   
 7 Channel habitat complexity     Non-tree-lined    NA 0.456 2.083333333 3.3333…
 8 Channel habitat complexity     Tree-lined        NA 0.618 1.25        2.9166…
 9 Number of bed material types   Non-tree-lined    NA 0.837 2           4      
10 Number of bed material types   Tree-lined        NA 0.707 1           3      
11 Number of flow types           Non-tree-lined    NA 0.447 1           2      
12 Number of flow types           Tree-lined        NA 0     1           1      
13 Riparian habitat complexity    Non-tree-lined    NA 0.310 2.833333333 3.5    
14 Riparian habitat complexity    Tree-lined        NA 0.414 3           4.0714…
15 Riparian vegetation complexity Non-tree-lined    NA 0.354 3.5         4.5    
16 Riparian vegetation complexity Tree-lined        NA 1.30  4           7      
# export MoRPH descriptive statistics table for appendix
# format table
morph_summary_table <- morph_summary %>%
  rename("MoRPH index" = metric,
  "Riparian condition" = condition) %>%
  mutate(across(where(is.numeric), ~ round(.x, 2)))
morph_summary_table
# A tibble: 16 × 6
   `MoRPH index`                `Riparian condition`  Mean    SD Minimum Maximum
   <chr>                        <chr>                <dbl> <dbl> <chr>   <chr>  
 1 Aquatic vegetation morphoty… Non-tree-lined          NA  0.84 2       4      
 2 Aquatic vegetation morphoty… Tree-lined              NA  0.55 1       2      
 3 Bank-top human pressure      Non-tree-lined          NA  0.82 3.5     5      
 4 Bank-top human pressure      Tree-lined              NA  0    3.5     3.5    
 5 Bed siltation                Non-tree-lined          NA  1.22 0.1     3.35   
 6 Bed siltation                Tree-lined              NA  1.58 3.55    7.65   
 7 Channel habitat complexity   Non-tree-lined          NA  0.46 2.0833… 3.3333…
 8 Channel habitat complexity   Tree-lined              NA  0.62 1.25    2.9166…
 9 Number of bed material types Non-tree-lined          NA  0.84 2       4      
10 Number of bed material types Tree-lined              NA  0.71 1       3      
11 Number of flow types         Non-tree-lined          NA  0.45 1       2      
12 Number of flow types         Tree-lined              NA  0    1       1      
13 Riparian habitat complexity  Non-tree-lined          NA  0.31 2.8333… 3.5    
14 Riparian habitat complexity  Tree-lined              NA  0.41 3       4.0714…
15 Riparian vegetation complex… Non-tree-lined          NA  0.35 3.5     4.5    
16 Riparian vegetation complex… Tree-lined              NA  1.3  4       7      
# format as flextable
morph_summary_table <- morph_summary_table %>%
  flextable() %>%
  theme_booktabs() %>%
  autofit() %>%
  align(align = "center", part = "all")
morph_summary_table

MoRPH index

Riparian condition

Mean

SD

Minimum

Maximum

Aquatic vegetation morphotypes

Non-tree-lined

0.84

2

4

Aquatic vegetation morphotypes

Tree-lined

0.55

1

2

Bank-top human pressure

Non-tree-lined

0.82

3.5

5

Bank-top human pressure

Tree-lined

0.00

3.5

3.5

Bed siltation

Non-tree-lined

1.22

0.1

3.35

Bed siltation

Tree-lined

1.58

3.55

7.65

Channel habitat complexity

Non-tree-lined

0.46

2.083333333

3.333333333

Channel habitat complexity

Tree-lined

0.62

1.25

2.916666667

Number of bed material types

Non-tree-lined

0.84

2

4

Number of bed material types

Tree-lined

0.71

1

3

Number of flow types

Non-tree-lined

0.45

1

2

Number of flow types

Tree-lined

0.00

1

1

Riparian habitat complexity

Non-tree-lined

0.31

2.833333333

3.5

Riparian habitat complexity

Tree-lined

0.41

3

4.071428571

Riparian vegetation complexity

Non-tree-lined

0.35

3.5

4.5

Riparian vegetation complexity

Tree-lined

1.30

4

7

# save to appendix
save_as_docx(morph_summary_table, path = "outputs/tables/A_morph_descriptive.docx")

3. Descriptive spatial plots

# creating a plot for descriptive exploration of indices
# change values to numbers and round axis labels to 1 decimal place for visual purposes
morph_plot_data <- morph_plot_data %>%
  mutate(value = as.numeric(value))

morph_spatial_plot <- ggplot(morph_plot_data,
                             aes(x = module_position,
                                 y = value,
                                 group = condition,
                                 colour = condition)) +
  geom_line() +
  geom_point(size = 2) +
  facet_wrap(~ metric,
             scales = "free_y",
             ncol = 2) +
  scale_colour_manual(values = c("Tree-lined" = "#F8766D",
                                 "Non-tree-lined" = "#00BFC4")) +
  scale_y_continuous(labels = scales::number_format(accuracy = 0.1)) +
  labs(x = "Module",
       y = "Index value",
       colour = NULL ) +
  theme_light() +
  theme(legend.position = "bottom",
        legend.margin = margin(t = -10),
        legend.title = element_blank(),
        panel.spacing = unit(0.3, "lines"))
morph_spatial_plot

# export plot
ggsave("outputs/figures/1_morph_spatial.png",
       plot = morph_spatial_plot,
       width = 18,
       height = 15,
       units = "cm",
       dpi = 300)

4. Summarising index data

morph_summary <- morph_plot_data %>%
  group_by(metric, condition) %>%
  summarise(mean = mean(value, na.rm = TRUE),
            sd = sd(value, na.rm = TRUE),
            min = min(value, na.rm = TRUE),
            max = max(value, na.rm = TRUE),
            .groups = "drop")
view(morph_summary)


# check five modules in each riparian condition for every selected MoRPH index
table(morph_plot_data$metric, morph_plot_data$condition)
                                
                                 Non-tree-lined Tree-lined
  Aquatic vegetation morphotypes              5          5
  Bank-top human pressure                     5          5
  Bed siltation                               5          5
  Channel habitat complexity                  5          5
  Number of bed material types                5          5
  Number of flow types                        5          5
  Riparian habitat complexity                 5          5
  Riparian vegetation complexity              5          5
morph_plot_data %>%
  group_by(metric, condition) %>%
  summarise(n = n(),
            .groups = "drop")
# A tibble: 16 × 3
   metric                         condition          n
   <chr>                          <chr>          <int>
 1 Aquatic vegetation morphotypes Non-tree-lined     5
 2 Aquatic vegetation morphotypes Tree-lined         5
 3 Bank-top human pressure        Non-tree-lined     5
 4 Bank-top human pressure        Tree-lined         5
 5 Bed siltation                  Non-tree-lined     5
 6 Bed siltation                  Tree-lined         5
 7 Channel habitat complexity     Non-tree-lined     5
 8 Channel habitat complexity     Tree-lined         5
 9 Number of bed material types   Non-tree-lined     5
10 Number of bed material types   Tree-lined         5
11 Number of flow types           Non-tree-lined     5
12 Number of flow types           Tree-lined         5
13 Riparian habitat complexity    Non-tree-lined     5
14 Riparian habitat complexity    Tree-lined         5
15 Riparian vegetation complexity Non-tree-lined     5
16 Riparian vegetation complexity Tree-lined         5
# compare mean index values between riparian conditions
morph_comparison <- morph_summary %>%
  select(metric, condition, mean) %>%
  pivot_wider(names_from = condition,
              values_from = mean) %>%
  mutate(difference = `Non-tree-lined` - `Tree-lined`)
morph_comparison
# A tibble: 8 × 4
  metric                         `Non-tree-lined` `Tree-lined` difference
  <chr>                                     <dbl>        <dbl>      <dbl>
1 Aquatic vegetation morphotypes             2.8          1.4       1.4  
2 Bank-top human pressure                    4.1          3.5       0.600
3 Bed siltation                              1.26         6.28     -5.02 
4 Channel habitat complexity                 2.58         2.17      0.417
5 Number of bed material types               2.8          2         0.8  
6 Number of flow types                       1.8          1         0.8  
7 Riparian habitat complexity                3.28         3.66     -0.381
8 Riparian vegetation complexity             4            5.7      -1.7  
# rounding values
morph_comparison <- morph_comparison %>%
  mutate(`Non-tree-lined` = round(`Non-tree-lined`, 2),
         `Tree-lined` = round(`Tree-lined`, 2),
         difference = round(difference, 2))

# rename headers
morph_comparison <- morph_comparison %>%
  rename(`MoRPH index` = metric,
         Difference = difference)
morph_comparison
# A tibble: 8 × 4
  `MoRPH index`                  `Non-tree-lined` `Tree-lined` Difference
  <chr>                                     <dbl>        <dbl>      <dbl>
1 Aquatic vegetation morphotypes             2.8          1.4        1.4 
2 Bank-top human pressure                    4.1          3.5        0.6 
3 Bed siltation                              1.26         6.28      -5.02
4 Channel habitat complexity                 2.58         2.17       0.42
5 Number of bed material types               2.8          2          0.8 
6 Number of flow types                       1.8          1          0.8 
7 Riparian habitat complexity                3.28         3.66      -0.38
8 Riparian vegetation complexity             4            5.7       -1.7 
# export table for main text
morph_comparison_table <- morph_comparison %>%
  flextable() %>%
  theme_booktabs() %>%
  autofit() %>%
  align(align = "center", part = "all")

save_as_docx(morph_comparison_table, path = "outputs/tables/morph_comparison.docx")