library(tidyverse)
library(rlang)
library(broom)
library(quickpsy) # to use qpdat
fit_glm <- function(.data, .response, .predictor) {
  
  predictor <- ensym(.predictor)

  prop <- .data |>
    group_by({{ .predictor }}) |>
    summarise(k = sum({{.response}}), 
              n = n(),
              prop = mean({{ .response }}), .groups = "keep")

  model <- glm(inject(cbind(k, n - k) ~ !!predictor),
                      family = binomial(logit),
                      data = prop)

  param <- tidy(model) |>
    select(term, estimate) |>
    pivot_wider(names_from = term, values_from = estimate) |>
    rename(intercept = `(Intercept)`, sensitivity = {{ .predictor }}) |>
    mutate(bias = -intercept / sensitivity)

  min_max <- .data |>
    distinct({{.predictor}}) |>
    summarise(min = min({{ .predictor }}),
              max = max({{ .predictor }}))

  sequ <- tibble({{.predictor}} := seq(min_max$min,
                                       min_max$max,
                                       length.out = 100))

  psy <- augment(model, newdata = sequ, type.predict = "response") |>
    rename(prop = .fitted)
  
  deviance <- glance(model) |> 
    select(null.deviance, df.null) |> 
    mutate(p_value = pchisq(null.deviance, df = df.null, lower.tail = FALSE)) |>
    rename(null_deviance = null.deviance, df_null = df.null)

  list(prop = prop,
       param = param,
       psy = psy, 
       deviance = deviance)
}

Single group case

fit_all <- fit_glm(qpdat, resp, phase)
ggplot() +
  geom_point(data = fit_all$prop,
             aes(x = phase, y = prop)) +
  geom_line(data = fit_all$psy, 
            aes(x = phase, y = prop)) +
  geom_segment(data = fit_all$param, 
               aes(x = bias, xend = bias, 
                   y = 0, yend = .5))

Multiple groups

fits <- qpdat |> 
  group_by(participant, cond) |> 
  reframe(fit = list(fit_glm(cur_data(), resp, phase))) |> 
  unnest_wider(fit)
proportions <- fits |> 
  select(participant, cond, prop) |> 
  unnest(prop)
psy_functions <- fits |> 
  select(participant, cond, psy) |> 
  unnest(psy)
params <- fits |> 
  select(participant, cond, param) |> 
  unnest(param)
ggplot() +
  facet_wrap(vars(participant)) +
  geom_point(data = proportions,
             aes(x = phase, y = prop, color = cond)) +
  geom_line(data = psy_functions, 
            aes(x = phase, y = prop, color = cond)) +
  geom_segment(data = params, 
               aes(x = bias, xend = bias, 
                   y = 0, yend = .5, color = cond))

All the fits are different from a fit with a flat slope

 fits |> 
  select(participant, cond, deviance) |> 
  unnest(deviance)
LS0tCnRpdGxlOiAiRml0IGdsbSBmdW5jdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHJsYW5nKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KHF1aWNrcHN5KSAjIHRvIHVzZSBxcGRhdApgYGAKCgpgYGB7cn0KZml0X2dsbSA8LSBmdW5jdGlvbiguZGF0YSwgLnJlc3BvbnNlLCAucHJlZGljdG9yKSB7CiAgCiAgcHJlZGljdG9yIDwtIGVuc3ltKC5wcmVkaWN0b3IpCgogIHByb3AgPC0gLmRhdGEgfD4KICAgIGdyb3VwX2J5KHt7IC5wcmVkaWN0b3IgfX0pIHw+CiAgICBzdW1tYXJpc2UoayA9IHN1bSh7ey5yZXNwb25zZX19KSwgCiAgICAgICAgICAgICAgbiA9IG4oKSwKICAgICAgICAgICAgICBwcm9wID0gbWVhbih7eyAucmVzcG9uc2UgfX0pLCAuZ3JvdXBzID0gImtlZXAiKQoKICBtb2RlbCA8LSBnbG0oaW5qZWN0KGNiaW5kKGssIG4gLSBrKSB+ICEhcHJlZGljdG9yKSwKICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSA9IGJpbm9taWFsKGxvZ2l0KSwKICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBwcm9wKQoKICBwYXJhbSA8LSB0aWR5KG1vZGVsKSB8PgogICAgc2VsZWN0KHRlcm0sIGVzdGltYXRlKSB8PgogICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHRlcm0sIHZhbHVlc19mcm9tID0gZXN0aW1hdGUpIHw+CiAgICByZW5hbWUoaW50ZXJjZXB0ID0gYChJbnRlcmNlcHQpYCwgc2Vuc2l0aXZpdHkgPSB7eyAucHJlZGljdG9yIH19KSB8PgogICAgbXV0YXRlKGJpYXMgPSAtaW50ZXJjZXB0IC8gc2Vuc2l0aXZpdHkpCgogIG1pbl9tYXggPC0gLmRhdGEgfD4KICAgIGRpc3RpbmN0KHt7LnByZWRpY3Rvcn19KSB8PgogICAgc3VtbWFyaXNlKG1pbiA9IG1pbih7eyAucHJlZGljdG9yIH19KSwKICAgICAgICAgICAgICBtYXggPSBtYXgoe3sgLnByZWRpY3RvciB9fSkpCgogIHNlcXUgPC0gdGliYmxlKHt7LnByZWRpY3Rvcn19IDo9IHNlcShtaW5fbWF4JG1pbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX21heCRtYXgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aC5vdXQgPSAxMDApKQoKICBwc3kgPC0gYXVnbWVudChtb2RlbCwgbmV3ZGF0YSA9IHNlcXUsIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpIHw+CiAgICByZW5hbWUocHJvcCA9IC5maXR0ZWQpCiAgCiAgZGV2aWFuY2UgPC0gZ2xhbmNlKG1vZGVsKSB8PiAKICAgIHNlbGVjdChudWxsLmRldmlhbmNlLCBkZi5udWxsKSB8PiAKICAgIG11dGF0ZShwX3ZhbHVlID0gcGNoaXNxKG51bGwuZGV2aWFuY2UsIGRmID0gZGYubnVsbCwgbG93ZXIudGFpbCA9IEZBTFNFKSkgfD4KICAgIHJlbmFtZShudWxsX2RldmlhbmNlID0gbnVsbC5kZXZpYW5jZSwgZGZfbnVsbCA9IGRmLm51bGwpCgogIGxpc3QocHJvcCA9IHByb3AsCiAgICAgICBwYXJhbSA9IHBhcmFtLAogICAgICAgcHN5ID0gcHN5LCAKICAgICAgIGRldmlhbmNlID0gZGV2aWFuY2UpCn0KYGBgCgoKIyMgU2luZ2xlIGdyb3VwIGNhc2UKCmBgYHtyfQpmaXRfYWxsIDwtIGZpdF9nbG0ocXBkYXQsIHJlc3AsIHBoYXNlKQpgYGAKCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGZpdF9hbGwkcHJvcCwKICAgICAgICAgICAgIGFlcyh4ID0gcGhhc2UsIHkgPSBwcm9wKSkgKwogIGdlb21fbGluZShkYXRhID0gZml0X2FsbCRwc3ksIAogICAgICAgICAgICBhZXMoeCA9IHBoYXNlLCB5ID0gcHJvcCkpICsKICBnZW9tX3NlZ21lbnQoZGF0YSA9IGZpdF9hbGwkcGFyYW0sIAogICAgICAgICAgICAgICBhZXMoeCA9IGJpYXMsIHhlbmQgPSBiaWFzLCAKICAgICAgICAgICAgICAgICAgIHkgPSAwLCB5ZW5kID0gLjUpKQpgYGAKCgpNdWx0aXBsZSBncm91cHMKCmBgYHtyfQpmaXRzIDwtIHFwZGF0IHw+IAogIGdyb3VwX2J5KHBhcnRpY2lwYW50LCBjb25kKSB8PiAKICByZWZyYW1lKGZpdCA9IGxpc3QoZml0X2dsbShjdXJfZGF0YSgpLCByZXNwLCBwaGFzZSkpKSB8PiAKICB1bm5lc3Rfd2lkZXIoZml0KQpgYGAKCmBgYHtyfQpwcm9wb3J0aW9ucyA8LSBmaXRzIHw+IAogIHNlbGVjdChwYXJ0aWNpcGFudCwgY29uZCwgcHJvcCkgfD4gCiAgdW5uZXN0KHByb3ApCmBgYAoKYGBge3J9CnBzeV9mdW5jdGlvbnMgPC0gZml0cyB8PiAKICBzZWxlY3QocGFydGljaXBhbnQsIGNvbmQsIHBzeSkgfD4gCiAgdW5uZXN0KHBzeSkKYGBgCgpgYGB7cn0KcGFyYW1zIDwtIGZpdHMgfD4gCiAgc2VsZWN0KHBhcnRpY2lwYW50LCBjb25kLCBwYXJhbSkgfD4gCiAgdW5uZXN0KHBhcmFtKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArCiAgZmFjZXRfd3JhcCh2YXJzKHBhcnRpY2lwYW50KSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IHByb3BvcnRpb25zLAogICAgICAgICAgICAgYWVzKHggPSBwaGFzZSwgeSA9IHByb3AsIGNvbG9yID0gY29uZCkpICsKICBnZW9tX2xpbmUoZGF0YSA9IHBzeV9mdW5jdGlvbnMsIAogICAgICAgICAgICBhZXMoeCA9IHBoYXNlLCB5ID0gcHJvcCwgY29sb3IgPSBjb25kKSkgKwogIGdlb21fc2VnbWVudChkYXRhID0gcGFyYW1zLCAKICAgICAgICAgICAgICAgYWVzKHggPSBiaWFzLCB4ZW5kID0gYmlhcywgCiAgICAgICAgICAgICAgICAgICB5ID0gMCwgeWVuZCA9IC41LCBjb2xvciA9IGNvbmQpKQpgYGAKCgpBbGwgdGhlIGZpdHMgYXJlIGRpZmZlcmVudCBmcm9tIGEgZml0IHdpdGggYSBmbGF0IHNsb3BlCgpgYGB7cn0KIGZpdHMgfD4gCiAgc2VsZWN0KHBhcnRpY2lwYW50LCBjb25kLCBkZXZpYW5jZSkgfD4gCiAgdW5uZXN0KGRldmlhbmNlKQpgYGAKCg==