############################################################
## Code S1
## R script for reproducing the McNemar's tests reported in the paper
## 
## This script reproduces the statistical analyses described in
## Methods (Statistical analysis) using the datasets provided in
## Tables S1–S3.
## 
## Comparisons included:
## 1. Free-roaming cats: catnip plant and catnip material vs silver vine material (Table S1)
## 2. Captive purebred cats: catnip extract vs silver vine extract (Table S2)
## 3. Laboratory cats: cis-cis nepetalactone vs catnip extract (Table S3)
## 4. Laboratory cats: trans-cis nepetalactone vs catnip extract (Table S3)
## 
## All tests use a two-sided McNemar's test with mid-P correction.
############################################################

############################################################
## 1. Free-roaming cats: catnip plant and catnip material vs silver vine material
## Data source: Table S1, dataset = free_roaming_1
##
## Individual-level scoring:
## each cat was scored as 1 if it showed rubbing/rolling
## at least once to that stimulus across all visits, otherwise 0
############################################################

tableS1 <- read.csv("table S1.csv")
free_roaming_1 <- subset(tableS1, dataset == "free_roaming_1")

## Visit-level binary variables
## catnip = response to either intact catnip plant or catnip material
## silver_vine = response to silver vine material
free_roaming_1$catnip_any_visit <- ifelse(
  free_roaming_1$response_intact_catnip_plant == 1 |
    (!is.na(free_roaming_1$right_stimulus) &
       free_roaming_1$right_stimulus == "catnip_material" &
       !is.na(free_roaming_1$response_right) &
       free_roaming_1$response_right == 1),
  1, 0
)

free_roaming_1$silver_vine_any_visit <- ifelse(
  !is.na(free_roaming_1$left_stimulus) &
    free_roaming_1$left_stimulus == "silvervine_material" &
    !is.na(free_roaming_1$response_left) &
    free_roaming_1$response_left == 1,
  1, 0
)

## Aggregate to the individual level
free_roaming_1_individual <- aggregate(
  cbind(catnip_any_visit, silver_vine_any_visit) ~ cat_id,
  data = free_roaming_1,
  FUN = max
)

catnip <- free_roaming_1_individual$catnip_any_visit
silver_vine <- free_roaming_1_individual$silver_vine_any_visit

## 2 × 2 table structure
##                   silver_vine
## catnip              0      1
##                ----------------
## 0 (no response)      a      b
## 1 (response)         c      d
##
## discordant pairs:
## b = silver vine yes / catnip no
## c = catnip yes / silver vine no

tab1 <- table(
  factor(catnip, levels = c(0, 1)),
  factor(silver_vine, levels = c(0, 1)),
  dnn = c("catnip", "silver_vine")
)
tab1

## Extract discordant counts
b <- tab1["0", "1"]
c <- tab1["1", "0"]

n <- b + c
k <- min(b, c)

## Exact two-sided binomial p-value
p_exact <- min(1, 2 * pbinom(k, n, 0.5))

## Mid-P McNemar p-value
p_mid <- p_exact - dbinom(k, n, 0.5)

print(free_roaming_1_individual)
print(tab1)
cat("silver vine yes / catnip no =", b, "\n")
cat("catnip yes / silver vine no =", c, "\n")
cat("mid-P McNemar p =", p_mid, "\n\n")

############################################################
## 2. Captive purebred cats: catnip extract vs silver vine extract
## Data source: Table S2
##
## Individual-level scoring:
## each cat was scored as 1 if it showed rubbing/rolling
## at least once to that stimulus, otherwise 0
############################################################

tableS2 <- read.csv("table S2.csv")

tableS2$catnip_extract_any <- ifelse(
  (tableS2$left_stimulus == "catnip_extract" &
     tableS2$response_left == 1) |
    (tableS2$right_stimulus == "catnip_extract" &
       tableS2$response_right == 1),
  1, 0
)

tableS2$silver_vine_extract_any <- ifelse(
  (tableS2$left_stimulus == "silvervine_extract" &
     tableS2$response_left == 1) |
    (tableS2$right_stimulus == "silvervine_extract" &
       tableS2$response_right == 1),
  1, 0
)

## Aggregate to the individual level
tableS2_individual <- aggregate(
  cbind(catnip_extract_any, silver_vine_extract_any) ~ cat_id,
  data = tableS2,
  FUN = max
)

catnip_extract <- tableS2_individual$catnip_extract_any
silver_vine_extract <- tableS2_individual$silver_vine_extract_any

## 2 × 2 table structure
##                           silver_vine_extract
## catnip_extract                0          1
##                         ----------------------
## 0 (no response)               a          b
## 1 (response)                  c          d
##
## discordant pairs:
## b = silver vine extract yes / catnip extract no
## c = catnip extract yes / silver vine extract no

tab2 <- table(
  factor(catnip_extract, levels = c(0, 1)),
  factor(silver_vine_extract, levels = c(0, 1)),
  dnn = c("catnip_extract", "silver_vine_extract")
)
tab2

## Extract discordant counts
b <- tab2["0", "1"]
c <- tab2["1", "0"]

n <- b + c
k <- min(b, c)

p_exact <- min(1, 2 * pbinom(k, n, 0.5))
p_mid <- p_exact - dbinom(k, n, 0.5)

print(tableS2_individual)
print(tab2)
cat("silver vine extract yes / catnip extract no =", b, "\n")
cat("catnip extract yes / silver vine extract no =", c, "\n")
cat("mid-P McNemar p =", p_mid, "\n\n")

############################################################
## 3. Laboratory cats: cis-cis nepetalactone vs catnip extract
## Data source: Table S3, dataset = laboratory_cis-cis
##
## Individual-level scoring:
## each cat was scored as 1 if it showed rubbing/rolling
## at least once to that stimulus, otherwise 0
##
## In this assay, catnip extract was presented as a positive control,
## therefore response_positive_control was used for catnip extract.
############################################################

tableS3 <- read.csv("table S3.csv")
laboratory_cis_cis <- subset(tableS3, dataset == "laboratory_cis-cis")

laboratory_cis_cis$cis_cis_any <- ifelse(
  (laboratory_cis_cis$left_stimulus == "cis-cis_nepetalactone" &
     laboratory_cis_cis$response_left == 1) |
    (laboratory_cis_cis$right_stimulus == "cis-cis_nepetalactone" &
       laboratory_cis_cis$response_right == 1),
  1, 0
)

laboratory_cis_cis$catnip_extract_any <- laboratory_cis_cis$response_positive_control

## Aggregate to the individual level
laboratory_cis_cis_individual <- aggregate(
  cbind(cis_cis_any, catnip_extract_any) ~ cat_id,
  data = laboratory_cis_cis,
  FUN = max
)

cis_cis_nepetalactone <- laboratory_cis_cis_individual$cis_cis_any
catnip_extract <- laboratory_cis_cis_individual$catnip_extract_any

## 2 × 2 table structure
##                            catnip_extract
## cis_cis_nepetalactone       0        1
##                       -------------------
## 0 (no response)             a        b
## 1 (response)                c        d
##
## discordant pairs:
## b = catnip extract yes / cis-cis no
## c = cis-cis yes / catnip extract no

tab3 <- table(
  factor(cis_cis_nepetalactone, levels = c(0, 1)),
  factor(catnip_extract, levels = c(0, 1)),
  dnn = c("cis_cis_nepetalactone", "catnip_extract")
)
tab3

## Extract discordant counts
b <- tab3["0", "1"]
c <- tab3["1", "0"]

n <- b + c
k <- min(b, c)

p_exact <- min(1, 2 * pbinom(k, n, 0.5))
p_mid <- p_exact - dbinom(k, n, 0.5)

print(laboratory_cis_cis_individual)
print(tab3)
cat("catnip extract yes / cis-cis no =", b, "\n")
cat("cis-cis yes / catnip extract no =", c, "\n")
cat("mid-P McNemar p =", p_mid, "\n\n")

############################################################
## 4. Laboratory cats: trans-cis nepetalactone vs catnip extract
## Data source: Table S3, dataset = laboratory_trans-cis
##
## Individual-level scoring:
## each cat was scored as 1 if it showed rubbing/rolling
## at least once to that stimulus, otherwise 0
##
## In this assay, catnip extract was presented as a positive control,
## therefore response_positive_control was used for catnip extract.
############################################################

laboratory_trans_cis <- subset(tableS3, dataset == "laboratory_trans-cis")

laboratory_trans_cis$trans_cis_any <- ifelse(
  (laboratory_trans_cis$left_stimulus == "trans-cis_nepetalactone" &
     laboratory_trans_cis$response_left == 1) |
    (laboratory_trans_cis$right_stimulus == "trans-cis_nepetalactone" &
       laboratory_trans_cis$response_right == 1),
  1, 0
)

laboratory_trans_cis$catnip_extract_any <- laboratory_trans_cis$response_positive_control

## Aggregate to the individual level
laboratory_trans_cis_individual <- aggregate(
  cbind(trans_cis_any, catnip_extract_any) ~ cat_id,
  data = laboratory_trans_cis,
  FUN = max
)

trans_cis_nepetalactone <- laboratory_trans_cis_individual$trans_cis_any
catnip_extract <- laboratory_trans_cis_individual$catnip_extract_any

## 2 × 2 table structure
##                              catnip_extract
## trans_cis_nepetalactone       0        1
##                        -------------------
## 0 (no response)               a        b
## 1 (response)                  c        d
##
## discordant pairs:
## b = catnip extract yes / trans-cis no
## c = trans-cis yes / catnip extract no

tab4 <- table(
  factor(trans_cis_nepetalactone, levels = c(0, 1)),
  factor(catnip_extract, levels = c(0, 1)),
  dnn = c("trans_cis_nepetalactone", "catnip_extract")
)
tab4

## Extract discordant counts
b <- tab4["0", "1"]
c <- tab4["1", "0"]

n <- b + c
k <- min(b, c)

p_exact <- min(1, 2 * pbinom(k, n, 0.5))
p_mid <- p_exact - dbinom(k, n, 0.5)

print(laboratory_trans_cis_individual)
print(tab4)
cat("catnip extract yes / trans-cis no =", b, "\n")
cat("trans-cis yes / catnip extract no =", c, "\n")
cat("mid-P McNemar p =", p_mid, "\n\n")
