Phase 4: quantum-like semantic metrics and human judgments

Theory-guided analytical notebook with methodological rationale, salience variants, annex condition analyses, and human-aligned results

Author

Alejandro Martínez-Mingo

Published

July 2, 2026

1 Purpose and analytical strategy

This notebook is not only a results report. It is a theory-guided analytical notebook whose purpose is to document what was done, why it was done, which arbitrary decisions were made, and how the results should be interpreted within the quantum-like theory of conceptual representation developed in the thesis and manuscript.

The empirical strategy is human-aligned. Instead of computing all possible pairs and triplets among the 120 available concepts, the analyses are restricted to the pairs, triplets, and diagnostic designs that were actually used in the human studies. This is important because the computational model is being evaluated as a model of psychological judgments, not as an exercise in filling storage with elegant but irrelevant combinations.

The central question is whether representing concepts as subspaces, projectors, and density-like operators adds explanatory value beyond classical vector similarity. The benchmark is deliberately strong: cosine, angular distance, Euclidean distance, and dot product between saturated-contour centroids. If the quantum-like model only reproduced what a centroid cosine already explains, then the subspace machinery would be decorative algebra. Useful decorative algebra, perhaps, but still decorative.

The report compares these families:

  1. Classical vector baselines, especially cosine between saturated-contour centroids.
  2. Subspace/projector geometry, including overlap, containment, principal angles, Bures-like distances, and commutator-based incompatibility.
  3. Operator/density metrics, including Hilbert–Schmidt similarity and trace-normalized containment between projectors or density-like objects.
  4. Sequential QSM metrics, using projection sequences as state-dependent similarity measures.
  5. Contextualized diagnostic metrics, where a context acts as a coordinate frame or Lüders-style filter.
  6. Perplexity-inhibited metrics, where shared properties of a competing pair are removed before comparing the initial concept with each target.

The interpretive criterion is simple: a metric family is theoretically relevant when it predicts the human effect it was designed to capture, and especially when it does so better than classical centroid similarity.

2 Methodological pipeline and theoretical commitments

2.1 Phase 1: container semantic space

The analysis assumes a common semantic container space (H), operationalized as a 300-dimensional LSA/SVD space estimated from a large Spanish news corpus. This container is not interpreted as a psychological space by itself. It is the representational substrate in which vectors, subspaces, projectors, and density-like objects can all be expressed. This follows the manuscript’s proposal that concepts and contexts can be represented as subspaces inside a shared Hilbert-like container space.

The first controlled simplification is that the container is real-valued rather than complex-valued. This is an arbitrary but defensible decision: it preserves the geometry needed for projection, containment, angles, and density-like comparisons, while avoiding phase parameters that would be impossible to estimate from the present data without inventing them with suspicious confidence.

2.2 Phase 2: human-aligned empirical targets

The human data define the target phenomena: ipsative asymmetry, salience in two forms (ipsative forced choice and single-concept Likert salience), planned salience/similarity stimulus conditions, Likert similarity, order asymmetry, triangular structure, and diagnosticity. The computational analyses are therefore not exhaustive over the concept set. They are restricted to the same conceptual pairs, triplets, and diagnostic configurations observed in the human tasks.

This decision matters because the goal is not to discover any relation in the semantic space; the goal is to test whether the formal objects generated by the model reproduce the specific psychological comparisons measured in humans.

2.3 Phase 3A: saturated contours

For each concept, a saturated contour was built from all corpus occurrences that matched the target. A contour is a cloud of contextual document vectors, not yet a concept subspace. Methodologically, this distinguishes the empirical distribution of a word or concept in language from the formal object that will represent its conceptual structure.

The key decision is to use the contour centroid as the strongest classical baseline. The centroid is a useful classical summary because it collapses a concept’s contextual variability into one vector. If explicit similarity ratings are well predicted by this centroid, that suggests that some judgments are approximately average-proximity judgments. If asymmetry or diagnosticity are not well predicted by the centroid, that is exactly where subspace and operator structure become theoretically interesting.

2.4 Phase 3B: dimensionality and local semantic regions

Conceptual subspaces were estimated from saturated contours by selecting dimensionality and, where appropriate, local semantic regions. The main arbitrary decisions are the dimensionality rule and the clustering model. These were not treated as single hidden commitments. Instead, the analysis keeps multiple variants: Bayesian GMM versus GMM-BIC for local regions, and several dimensionality rules such as Horn/Wishart, permutation parallel analysis, Gavish-Donoho, and profile likelihood.

This is not methodological indecision wearing a lab coat. The point is sensitivity analysis. If a result only appears under one fragile variant, it should be interpreted cautiously. If it appears across families of variants, it is more likely to reflect a stable property of the representation.

2.5 Phase 3C: formal conceptual objects

From each contour and each dimensionality/local-region decision, the pipeline builds several formal objects:

  • (S_A): an orthonormal basis for concept (A).
  • (P_A = S_A S_A^T): the projector onto the conceptual subspace.
  • (_A = P_A / Tr(P_A)): a normalized density-like representation of the subspace.
  • (_{freq}): a frequency-weighted mixture of local semantic-region densities.
  • (_{fit}): a fitted mixture intended to approximate the global structure using local regions.
  • (P_{Lowdin}): a Löwdin-orthogonalized representation of concatenated local regions.

This is where the model becomes genuinely different from a vector model. A vector says “where the concept points on average.” A subspace says “which directions are relevant to the concept.” A density-like object says “how semantic mass is distributed across relevant directions.” Psychological similarity can then be tested as overlap, containment, distinguishability, contextual filtering, or order-dependent projection.

2.6 Phase 4: human-aligned metric evaluation

Phase 4 evaluates whether these formal objects predict human judgments. The important design decision is to evaluate families of metrics, not one cherished formula. The original QSM based on sequential projections is included, but it is not treated as the whole model. The thesis and manuscript motivate a wider algebra: projectors, containment, density-like overlap, contextualized states, incompatibility, and residual states after perplejity-like inhibition.

The results are interpreted at two levels:

  1. Local hypothesis level: which metric best predicts each human effect?
  2. Theoretical level: what does that success or failure imply about the psychological operation involved?

This is the distinction that prevents the notebook from becoming a leaderboard with Greek letters.

2.7 Planned salience and similarity conditions from the thesis annex

The thesis annex does not only list country pairs. It organizes the asymmetry/salience stimuli into a small factorial structure: concepts can be high or low in planned salience, and pairs can be high or low in planned similarity. This creates cells such as low–low / low similarity, low–low / high similarity, high–low / low similarity, high–low / high similarity, high–high / low similarity, and high–high / high similarity. In the rendered experimental files, these same pairs may appear in either order, so the analysis needs to distinguish the planned conceptual condition from the actual presentation order.

This distinction matters because it gives the notebook an additional layer of theory-guided analysis:

  1. Manipulation check for salience. If the annex labels are meaningful, single-concept Likert salience should be higher for concepts labelled high-salience than for those labelled low-salience.
  2. Ipsative salience check. In high–low and low–high pair presentations, participants should preferentially choose the high-salience concept. In high–high and low–low pairs, choice should be closer to chance.
  3. Similarity manipulation check. Pairs labelled high similarity should receive higher Likert similarity ratings and higher computational similarity values than low-similarity pairs.
  4. Asymmetry moderation. Directional containment should matter most when the pair has an unequal salience structure. Equal-salience pairs are less diagnostic for asymmetry.
  5. Dimensionality interpretation. If Likert salience is concept-level rather than pairwise, dimensionality and corpus frequency become legitimate predictors of salience. This is not the same mechanism as ipsative salience: one concerns global representational availability, the other concerns directional comparison inside a pair.
  6. Cross-contrast moderation. The salience/similarity cells should also qualify other contrasts: Likert order effects, explicit similarity ratings, and the strength of computational-human coupling inside each condition.
  7. Diagnostic context as priming-like manipulation. The thesis does not define a separate priming task in the Phase 2 instruments. The closest experimental analogue is diagnosticity: a context/distractor is inserted into the comparison set and should activate, inhibit, or reweight diagnostic features. For this reason, the notebook treats diagnostic context as a priming-like contextual manipulation and analyzes whether context-target similarity predicts human choices and ratings.

The notebook therefore adds a condition-level block without changing the previous result tables. The condition analyses are treated as theory-guided diagnostics, not as a replacement for the main H1–H6 analyses.

2.8 Arbitrary decisions and how they are controlled

The following decisions are arbitrary in the technical sense that alternatives are possible:

  • The container dimensionality is fixed at 300.
  • The model uses real-valued LSA coordinates.
  • Concept contours are based on corpus occurrence retrieval.
  • Subspace dimensionality depends on statistical selection rules.
  • Local semantic regions depend on clustering assumptions.
  • Density-like objects are normalized projectors or mixtures, not empirically estimated quantum density matrices.
  • Distances are reversed when the dependent human variable is similarity.
  • Directional metrics are reoriented to match the human item order.
  • Diagnostic perplexity uses a soft intersection/residual approximation rather than an exact psychological process model.

The notebook controls these decisions in three ways: by including classical baselines, by comparing multiple quantum-like metric families, and by reporting representation/variant sensitivity.

3 Packages and paths

Show code
required_packages <- c(
  "tidyverse", "vroom", "broom", "knitr", "scales", "stringr", "forcats", "fs"
)

missing_packages <- required_packages[!vapply(required_packages, requireNamespace, logical(1), quietly = TRUE)]

if (length(missing_packages) > 0) {
  if (isTRUE(params$install_missing)) {
    install.packages(missing_packages)
  } else {
    stop(
      "Missing packages: ", paste(missing_packages, collapse = ", "),
      "\nInstall them or render with -P install_missing:true. R wants snacks before working."
    )
  }
}

library(tidyverse)
library(vroom)
library(broom)
library(knitr)
library(scales)
library(stringr)
library(forcats)
library(fs)
Show code
project_root <- normalizePath(getwd(), mustWork = TRUE)
data_dir <- file.path(project_root, params$data_dir)
empirical_dir <- file.path(project_root, "Empirical_data")

clean_paths <- function(paths) {
  paths <- unlist(paths, use.names = FALSE)
  paths <- paths[!is.na(paths) & nzchar(paths)]
  unique(paths)
}

first_existing <- function(paths, type = c("file", "dir"), required = TRUE, label = "path") {
  type <- match.arg(type)
  paths <- clean_paths(paths)
  exists_fun <- if (type == "file") file.exists else dir.exists
  hit <- paths[exists_fun(paths)]
  if (length(hit) > 0) return(normalizePath(hit[[1]], mustWork = TRUE))
  if (required) {
    stop(
      "Could not find ", label, ". Tried:\n",
      paste(paths, collapse = "\n"),
      "\n\nCheck folder names or pass the explicit path with a Quarto parameter, e.g. -P phase4_dir:/your/path."
    )
  }
  NA_character_
}

find_parent_by_file <- function(roots, filename, max_depth = 5) {
  roots <- clean_paths(roots)
  roots <- roots[dir.exists(roots)]
  if (length(roots) == 0) return(character())

  hits <- character()
  for (r in roots) {
    files <- list.files(
      r,
      pattern = paste0("^", gsub("\\.", "\\\\.", filename), "$"),
      recursive = TRUE,
      full.names = TRUE,
      all.files = FALSE
    )
    if (length(files) > 0) {
      root_norm <- normalizePath(r, mustWork = FALSE)
      file_norm <- normalizePath(files, mustWork = FALSE)
      rel <- sub(paste0("^", root_norm, "/?"), "", file_norm)
      depth <- stringr::str_count(rel, .Platform$file.sep)
      files <- files[depth <= max_depth]
      hits <- c(hits, dirname(files))
    }
  }
  unique(hits)
}

phase4_candidates <- c(
  params$phase4_dir,
  file.path(data_dir, "quantum_metrics_human_aligned"),
  file.path(data_dir, "phase4_quantum_metrics_human_aligned"),
  file.path(project_root, "quantum_metrics_human_aligned"),
  file.path(project_root, "phase4_quantum_metrics_human_aligned"),
  find_parent_by_file(c(data_dir, project_root), "phase4_quantum_metrics_summary.json", max_depth = 4)
)

phase4_dir <- first_existing(phase4_candidates, type = "dir", label = "Phase 4 metrics directory")

dimensionality_candidates <- c(
  params$dimensionality_dir,
  file.path(data_dir, "dimensionality_decisions"),
  file.path(data_dir, "phase3b_dimensionality_decisions"),
  file.path(data_dir, "phase3b_dimensionality_decisions_bayesian_gmm_rank_safe_gmm30"),
  file.path(project_root, "dimensionality_decisions"),
  file.path(project_root, "phase3b_dimensionality_decisions"),
  find_parent_by_file(c(data_dir, project_root), "global_dimensionality_wide.csv", max_depth = 6)
)

dimensionality_dir <- first_existing(
  dimensionality_candidates,
  type = "dir",
  required = FALSE,
  label = "Phase 3B dimensionality directory"
)

phase2_candidates <- c(
  params$phase2_dir,
  file.path(empirical_dir, "tidy_outputs"),
  file.path(empirical_dir, "phase2_tidy_outputs"),
  file.path(data_dir, "phase2_tidy_outputs"),
  file.path(project_root, "phase2_tidy_outputs"),
  find_parent_by_file(c(empirical_dir, data_dir, project_root), "items_catalog_phase2.csv", max_depth = 5)
)

phase2_dir <- first_existing(phase2_candidates, type = "dir", required = FALSE, label = "Phase 2 tidy outputs directory")

stimuli_candidates <- c(
  params$stimuli_dir,
  file.path(data_dir, "stimuli", "human_stimuli_design"),
  file.path(data_dir, "stimuli", "phase4_human_stimuli_design"),
  file.path(data_dir, "human_stimuli_design"),
  file.path(data_dir, "phase4_human_stimuli_design"),
  file.path(project_root, "stimuli", "human_stimuli_design"),
  file.path(project_root, "stimuli", "phase4_human_stimuli_design"),
  file.path(project_root, "human_stimuli_design"),
  file.path(project_root, "phase4_human_stimuli_design"),
  find_parent_by_file(c(data_dir, project_root), "phase4_human_pairwise_design_model_ready.csv", max_depth = 6)
)

stimuli_dir <- first_existing(stimuli_candidates, type = "dir", label = "human stimuli design directory")

output_dir <- file.path(project_root, params$output_dir)
fs::dir_create(output_dir)
fs::dir_create(file.path(output_dir, "tables"))
fs::dir_create(file.path(output_dir, "figures"))
fs::dir_create(file.path(output_dir, "data"))

paths <- list(
  project_root = project_root,
  data_dir = data_dir,
  empirical_dir = empirical_dir,
  phase4_dir = phase4_dir,
  dimensionality_dir = dimensionality_dir,
  phase2_dir = phase2_dir,
  stimuli_dir = stimuli_dir,
  output_dir = output_dir
)

paths
$project_root
[1] "/home/alex/Documentos/RESEARCH/QuantumNLP/Paper1_StructuralModel"

$data_dir
[1] "/home/alex/Documentos/RESEARCH/QuantumNLP/Paper1_StructuralModel/Computational_data"

$empirical_dir
[1] "/home/alex/Documentos/RESEARCH/QuantumNLP/Paper1_StructuralModel/Empirical_data"

$phase4_dir
[1] "/home/alex/Documentos/RESEARCH/QuantumNLP/Paper1_StructuralModel/Computational_data/quantum_metrics_human_aligned"

$dimensionality_dir
[1] "/home/alex/Documentos/RESEARCH/QuantumNLP/Paper1_StructuralModel/Computational_data/dimensionality_decisions"

$phase2_dir
[1] "/home/alex/Documentos/RESEARCH/QuantumNLP/Paper1_StructuralModel/Empirical_data/tidy_outputs"

$stimuli_dir
[1] "/home/alex/Documentos/RESEARCH/QuantumNLP/Paper1_StructuralModel/Computational_data/stimuli/human_stimuli_design"

$output_dir
[1] "/home/alex/Documentos/RESEARCH/QuantumNLP/Paper1_StructuralModel/Computational_data/phase4_R_analysis_outputs_v12"

4 Helper functions

Show code
read_csv_fast <- function(path, col_select = NULL, required = TRUE) {
  if (!file.exists(path)) {
    if (required) stop("File not found: ", path)
    return(tibble())
  }
  if (file.info(path)$size <= 1) return(tibble())
  if (!is.null(col_select)) {
    vroom::vroom(path, delim = ",", show_col_types = FALSE, progress = FALSE, col_select = all_of(col_select))
  } else {
    vroom::vroom(path, delim = ",", show_col_types = FALSE, progress = FALSE)
  }
}

canonical_pair <- function(a, b) {
  paste(pmin(as.character(a), as.character(b)), pmax(as.character(a), as.character(b)), sep = "__")
}

apa_num <- function(x, digits = 2) {
  ifelse(is.na(x), "", sprintf(paste0("%0.", digits, "f"), x))
}

apa_p <- function(p) {
  case_when(
    is.na(p) ~ "",
    p < .001 ~ "< .001",
    TRUE ~ sub("^0", "", sprintf("%.3f", p))
  )
}

apa_r <- function(x, digits = 2) {
  out <- sprintf(paste0("%0.", digits, "f"), x)
  out <- str_replace(out, "^-0\\.", "−.")
  out <- str_replace(out, "^0\\.", ".")
  out
}

apa_table <- function(data, caption = NULL, digits = 3) {
  knitr::kable(
    data,
    format = "html",
    caption = caption,
    digits = digits,
    align = "l",
    booktabs = TRUE
  )
}

write_table <- function(data, filename) {
  readr::write_csv(data, file.path(output_dir, "tables", filename))
}

save_plot <- function(plot, filename, width = 8, height = 5) {
  ggplot2::ggsave(
    filename = file.path(output_dir, "figures", filename),
    plot = plot,
    width = width,
    height = height,
    dpi = 300
  )
}

theme_apa7 <- function(base_size = 11) {
  # This is no longer a strict APA plot theme. Tables remain APA-like, but plots are
  # optimized for readability in an analytical notebook: clearer grids, larger labels,
  # and a colorblind-friendly palette where possible.
  ggplot2::theme_minimal(base_size = base_size, base_family = "sans") +
    theme(
      panel.grid.minor = element_blank(),
      panel.grid.major.x = element_line(linewidth = .25, color = "grey84"),
      panel.grid.major.y = element_blank(),
      axis.line.x = element_line(linewidth = .35, color = "grey35"),
      axis.ticks.x = element_line(color = "grey35"),
      plot.title = element_text(face = "bold", size = base_size + 2, margin = margin(b = 5)),
      plot.subtitle = element_text(size = base_size, color = "grey25", margin = margin(b = 8)),
      plot.caption = element_text(size = base_size - 2, hjust = 0, color = "grey35"),
      axis.title = element_text(size = base_size, face = "bold"),
      axis.text = element_text(size = base_size - 1, color = "grey20"),
      legend.position = "bottom",
      legend.title = element_blank(),
      legend.text = element_text(size = base_size - 1),
      strip.background = element_rect(fill = "grey95", color = NA),
      strip.text = element_text(face = "bold"),
      plot.margin = margin(10, 16, 10, 10)
    )
}

report_palette <- c(
  "#0072B2", "#D55E00", "#009E73", "#CC79A7",
  "#E69F00", "#56B4E9", "#999999", "#F0E442"
)

safe_cor <- function(data, x = "metric_value", y = "human_value", method = "spearman") {
  d <- data %>%
    filter(!is.na(.data[[x]]), !is.na(.data[[y]])) %>%
    filter(is.finite(.data[[x]]), is.finite(.data[[y]]))

  if (nrow(d) < 3 || n_distinct(d[[x]]) < 2 || n_distinct(d[[y]]) < 2) {
    return(tibble(n = nrow(d), estimate = NA_real_, p.value = NA_real_))
  }

  ct <- suppressWarnings(cor.test(d[[x]], d[[y]], method = method, exact = FALSE))
  tibble(
    n = nrow(d),
    estimate = unname(ct$estimate),
    p.value = ct$p.value
  )
}

cor_by_group <- function(data, group_cols, x = "metric_value", y = "human_value", method = "spearman") {
  # Different Phase 4 tables do not all carry the same descriptor columns
  # (for instance, H3 order-averaged metrics do not need metric_direction).
  # Use the requested grouping variables when present, and silently add the
  # missing ones back as NA after the correlation step. This keeps downstream
  # tables stable without forcing every intermediate tibble to pretend it has
  # every column in the universe. Very considerate of us.
  group_cols <- unique(group_cols)
  present_group_cols <- intersect(group_cols, names(data))
  missing_group_cols <- setdiff(group_cols, names(data))

  if (!x %in% names(data)) stop("Column '", x, "' not found in data passed to cor_by_group().")
  if (!y %in% names(data)) stop("Column '", y, "' not found in data passed to cor_by_group().")

  out <- data %>%
    filter(!is.na(.data[[x]]), !is.na(.data[[y]]))

  if (length(present_group_cols) > 0) {
    out <- out %>%
      group_by(across(all_of(present_group_cols))) %>%
      group_modify(~ safe_cor(.x, x = x, y = y, method = method)) %>%
      ungroup()
  } else {
    out <- safe_cor(out, x = x, y = y, method = method)
  }

  if (length(missing_group_cols) > 0) {
    for (cc in missing_group_cols) out[[cc]] <- NA_character_
  }

  out %>%
    mutate(
      p_fdr = p.adjust(p.value, method = "BH"),
      abs_estimate = abs(estimate)
    ) %>%
    arrange(desc(abs_estimate), p.value)
}

is_asymmetry_metric <- function(metric_name) {
  str_detect(metric_name, regex("asymmetry|a_minus_b|first_minus_second|second_minus_first", ignore_case = TRUE))
}

is_distance_metric <- function(metric_name) {
  str_detect(metric_name, regex("distance|bures|angle|geodesic|frobenius|commutator", ignore_case = TRUE))
}

metric_family_from_source <- function(metric_source) {
  case_when(
    metric_source == "classical_baselines" ~ "Classical vector baseline",
    metric_source == "pairwise_subspace_metrics" ~ "Subspace/projector geometry",
    metric_source == "pairwise_operator_density_metrics" ~ "Operator/density geometry",
    metric_source == "pairwise_qsm_state_metrics" ~ "Sequential QSM",
    metric_source == "triplet_distance_violations" ~ "Triplet distance geometry",
    metric_source == "triplet_mti_violations" ~ "Triplet similarity geometry",
    str_detect(metric_source, "diagnostic_context") ~ "Contextualized diagnostic",
    str_detect(metric_source, "diagnostic_perplexity") ~ "Perplexity-inhibited diagnostic",
    TRUE ~ metric_source
  )
}

metric_short_label <- function(metric_name) {
  case_when(
    str_detect(metric_name, "containment_asymmetry") ~ "Containment asymmetry",
    str_detect(metric_name, "qsm_asymmetry") ~ "QSM asymmetry",
    str_detect(metric_name, "qsm_p_b_given_a") ~ "QSM p(B|A)",
    str_detect(metric_name, "qsm_p_a_given_b") ~ "QSM p(A|B)",
    str_detect(metric_name, "density_fidelity") ~ "Density fidelity",
    str_detect(metric_name, "density_bures") ~ "Bures distance, reversed",
    str_detect(metric_name, "trace_distance") ~ "Trace distance, reversed",
    str_detect(metric_name, "cosine_hilbert_schmidt") ~ "Hilbert-Schmidt cosine",
    str_detect(metric_name, "symmetric_projector_overlap") ~ "Projector overlap",
    str_detect(metric_name, "symmetric_trace_normalized_overlap") ~ "Trace-normalized overlap",
    str_detect(metric_name, "projector_chordal") ~ "Chordal distance, reversed",
    str_detect(metric_name, "mean_cosine_sq") ~ "Mean cos² principal angles",
    str_detect(metric_name, "mean_cosine") ~ "Mean principal cosine",
    str_detect(metric_name, "classical_.*cosine") ~ "Centroid cosine",
    str_detect(metric_name, "classical_.*angular_distance") ~ "Angular distance, reversed",
    str_detect(metric_name, "classical_.*euclidean_distance") ~ "Euclidean distance, reversed",
    str_detect(metric_name, "contextualized_vector_cosine") ~ "Contextualized vector cosine",
    str_detect(metric_name, "density_hs_inner") ~ "Contextual density HS inner",
    str_detect(metric_name, "perplexity_density_fidelity") ~ "Perplexity density fidelity",
    TRUE ~ str_replace_all(metric_name, "_", " ")
  )
}

variant_short <- function(x) {
  x <- coalesce(as.character(x), "")
  x <- str_replace_all(x, "clust-", "")
  x <- str_replace_all(x, "__g-", " | g=")
  x <- str_replace_all(x, "__l-", " | l=")
  x
}

metric_is_classical <- function(metric_source) metric_source == "classical_baselines"

similarity_orient_value <- function(metric_name, value) {
  ifelse(is_distance_metric(metric_name), -as.numeric(value), as.numeric(value))
}

orient_asymmetry_to_human_order <- function(metric_name, value, direction_sign) {
  ifelse(is_asymmetry_metric(metric_name) & !is.na(direction_sign), as.numeric(value) * direction_sign, as.numeric(value))
}

interpret_corr_strength <- function(r) {
  ar <- abs(r)
  case_when(
    is.na(ar) ~ "not estimable",
    ar >= .80 ~ "very strong",
    ar >= .60 ~ "strong",
    ar >= .40 ~ "moderate",
    ar >= .20 ~ "weak-to-moderate",
    TRUE ~ "weak"
  )
}

normalize_salience_target <- function(x) {
  x <- as.character(x)
  x <- str_to_lower(x)
  x <- str_replace_all(x, "á", "a")
  x <- str_replace_all(x, "é", "e")
  x <- str_replace_all(x, "í", "i")
  x <- str_replace_all(x, "ó", "o")
  x <- str_replace_all(x, "ú", "u")
  x <- str_replace_all(x, "ü", "u")
  x <- str_replace_all(x, "ñ", "n")
  x <- recode(
    x,
    "eeuu" = "estados_unidos",
    "uk" = "reino_unido",
    "mejico" = "mexico",
    "espana" = "espana",
    .default = x
  )
  x
}

z_score <- function(x) as.numeric(scale(x))

plot_ranked_correlations <- function(rank_df, title, subtitle = NULL, n = 10, filename = NULL) {
  d <- rank_df %>%
    filter(!is.na(estimate)) %>%
    slice_head(n = n) %>%
    mutate(
      label = paste0(metric_short_label(metric_name), "\n", str_wrap(metric_family_from_source(metric_source), 28)),
      label = fct_reorder(label, abs_estimate),
      significant = if_else(!is.na(p_fdr) & p_fdr < .05, "FDR < .05", "FDR ≥ .05")
    )

  p <- ggplot(d, aes(x = label, y = estimate, fill = significant)) +
    geom_hline(yintercept = 0, linewidth = .35) +
    geom_col(width = .72) +
    coord_flip() +
    scale_y_continuous(limits = c(-1, 1), breaks = seq(-1, 1, .25)) +
    scale_fill_manual(values = report_palette) +
    labs(title = title, subtitle = subtitle, x = NULL, y = "Spearman rho") +
    theme_apa7()

  if (!is.null(filename)) save_plot(p, filename, width = 8.5, height = max(4.5, .38 * n + 1.5))
  p
}

plot_metric_scatter <- function(data, rank_row, title, y_label, filename = NULL) {
  if (nrow(rank_row) == 0) return(NULL)
  d <- data %>% filter(metric_id == rank_row$metric_id[[1]])
  if (nrow(d) == 0) return(NULL)

  p <- ggplot(d, aes(x = metric_value, y = human_value)) +
    geom_point(size = 2.2, alpha = .80) +
    geom_smooth(method = "lm", se = TRUE, linewidth = .75) +
    labs(
      title = title,
      subtitle = paste0(
        metric_short_label(rank_row$metric_name[[1]]),
        "; rho = ", apa_r(rank_row$estimate[[1]]),
        ", p ", apa_p(rank_row$p.value[[1]])
      ),
      x = "Computational metric value",
      y = y_label
    ) +
    theme_apa7()

  if (!is.null(filename)) save_plot(p, filename)
  p
}

best_by_family <- function(rank_df) {
  rank_df %>%
    mutate(metric_family = metric_family_from_source(metric_source)) %>%
    group_by(metric_family) %>%
    slice_max(order_by = abs_estimate, n = 1, with_ties = FALSE) %>%
    ungroup() %>%
    arrange(desc(abs_estimate))
}

compare_quantum_vs_cosine <- function(rank_df) {
  d <- rank_df %>%
    mutate(
      comparison_family = case_when(
        metric_source == "classical_baselines" & str_detect(metric_name, "cosine") ~ "Best classical cosine",
        metric_source == "classical_baselines" ~ "Other classical baseline",
        TRUE ~ "Best quantum-like metric"
      )
    ) %>%
    filter(comparison_family %in% c("Best classical cosine", "Best quantum-like metric")) %>%
    group_by(comparison_family) %>%
    slice_max(order_by = abs_estimate, n = 1, with_ties = FALSE) %>%
    ungroup() %>%
    arrange(comparison_family)
  d
}

plot_family_comparison <- function(best_df, title, filename = NULL) {
  d <- best_df %>%
    mutate(
      metric_family = fct_reorder(metric_family, abs_estimate),
      label = paste0(metric_family, "\n", metric_short_label(metric_name))
    )
  p <- ggplot(d, aes(x = fct_reorder(label, abs_estimate), y = estimate)) +
    geom_hline(yintercept = 0, linewidth = .35) +
    geom_col(width = .72) +
    coord_flip() +
    scale_y_continuous(limits = c(-1, 1), breaks = seq(-1, 1, .25)) +
    labs(title = title, x = NULL, y = "Best Spearman rho within family") +
    theme_apa7()
  if (!is.null(filename)) save_plot(p, filename, width = 8.5, height = max(4.5, .55 * nrow(d) + 1.5))
  p
}

5 Load data

Show code
phase4_files <- list(
  summary = file.path(phase4_dir, "phase4_quantum_metrics_summary.json"),
  subspace = file.path(phase4_dir, "pairwise_subspace_metrics.csv"),
  operator_density = file.path(phase4_dir, "pairwise_operator_density_metrics.csv"),
  qsm_state = file.path(phase4_dir, "pairwise_qsm_state_metrics.csv"),
  pairwise_long = file.path(phase4_dir, "pairwise_metrics_long_for_human_merge.csv"),
  pairwise_human_prejoined = file.path(phase4_dir, "pairwise_metrics_with_human_behavior.csv"),
  classical = file.path(phase4_dir, "classical_baselines.csv"),
  triplet_distance = file.path(phase4_dir, "triplet_distance_violations.csv"),
  triplet_mti = file.path(phase4_dir, "triplet_mti_violations.csv"),
  diagnostic_context = file.path(phase4_dir, "diagnostic_contextualized_similarity.csv"),
  diagnostic_perplexity = file.path(phase4_dir, "diagnostic_perplexity_metrics.csv"),
  diagnostic_choice = file.path(phase4_dir, "diagnostic_choice_predictions.csv"),
  diagnostic_design_normalized = file.path(phase4_dir, "diagnostic_design_normalized.csv"),
  representation_alignment = file.path(phase4_dir, "representation_alignment.csv"),
  variant_sensitivity = file.path(phase4_dir, "variant_sensitivity.csv")
)

stimuli_files <- list(
  pairwise_design = file.path(stimuli_dir, "phase4_human_pairwise_design_model_ready.csv"),
  triplet_design = file.path(stimuli_dir, "phase4_human_triplet_design_model_ready.csv"),
  diagnostic_design = file.path(stimuli_dir, "phase4_diagnostic_design_model_ready.csv")
)

phase2_files <- list(
  saliency_2 = first_existing(
    c(
      file.path(phase2_dir, "analysis_ready_default_sources", "saliency_2.csv"),
      file.path(phase2_dir, "saliency_2.csv"),
      file.path(dirname(phase2_dir), "audit_outputs", "analysis_ready_default", "saliency_2.csv"),
      file.path(dirname(phase2_dir), "audit_outputs", "normalized_with_participant_id", "saliency_2.csv"),
      file.path(project_root, "saliency_2.csv"),
      file.path(find_parent_by_file(c(empirical_dir, data_dir, project_root), "saliency_2.csv", max_depth = 7), "saliency_2.csv")
    ),
    type = "file",
    required = FALSE,
    label = "single-concept Likert salience data, saliency_2.csv"
  )
)

dimensionality_files <- list(
  global = first_existing(
    c(
      file.path(dimensionality_dir, "global_dimensionality_wide.csv"),
      file.path(data_dir, "dimensionality_decisions", "global_dimensionality_wide.csv"),
      file.path(project_root, "global_dimensionality_wide.csv"),
      file.path(find_parent_by_file(c(data_dir, project_root), "global_dimensionality_wide.csv", max_depth = 7), "global_dimensionality_wide.csv")
    ),
    type = "file",
    required = FALSE,
    label = "global dimensionality decisions"
  ),
  local = first_existing(
    c(
      file.path(dimensionality_dir, "local_dimensionality_wide.csv"),
      file.path(data_dir, "dimensionality_decisions", "local_dimensionality_wide.csv"),
      file.path(project_root, "local_dimensionality_wide.csv"),
      file.path(find_parent_by_file(c(data_dir, project_root), "local_dimensionality_wide.csv", max_depth = 7), "local_dimensionality_wide.csv")
    ),
    type = "file",
    required = FALSE,
    label = "local dimensionality decisions"
  )
)

pair_design_raw <- read_csv_fast(stimuli_files$pairwise_design)
triplet_design <- read_csv_fast(stimuli_files$triplet_design)
diagnostic_design <- read_csv_fast(stimuli_files$diagnostic_design)
saliency_2_raw <- read_csv_fast(phase2_files$saliency_2, required = FALSE)
global_dimensionality_raw <- read_csv_fast(dimensionality_files$global, required = FALSE)
local_dimensionality_raw <- read_csv_fast(dimensionality_files$local, required = FALSE)

subspace_raw <- read_csv_fast(phase4_files$subspace)
operator_raw <- read_csv_fast(phase4_files$operator_density)
qsm_raw <- read_csv_fast(phase4_files$qsm_state)
classical_raw <- read_csv_fast(phase4_files$classical)
triplet_distance_raw <- read_csv_fast(phase4_files$triplet_distance)
triplet_mti_raw <- read_csv_fast(phase4_files$triplet_mti)
diag_context_raw <- read_csv_fast(phase4_files$diagnostic_context)
diag_perplexity_raw <- read_csv_fast(phase4_files$diagnostic_perplexity)
diag_choice_raw <- read_csv_fast(phase4_files$diagnostic_choice)
representation_alignment <- read_csv_fast(phase4_files$representation_alignment)
variant_sensitivity <- read_csv_fast(phase4_files$variant_sensitivity)

pairwise_long_raw <- if (isTRUE(params$read_pairwise_long) && file.exists(phase4_files$pairwise_long)) {
  read_csv_fast(phase4_files$pairwise_long)
} else {
  NULL
}

overview <- tibble(
  data_object = c(
    "pair_design_raw", "triplet_design", "diagnostic_design", "subspace_raw", "operator_raw", "qsm_raw",
    "classical_raw", "triplet_distance_raw", "triplet_mti_raw", "diag_context_raw", "diag_perplexity_raw",
    "diag_choice_raw", "representation_alignment", "variant_sensitivity"
  ),
  rows = c(
    nrow(pair_design_raw), nrow(triplet_design), nrow(diagnostic_design), nrow(subspace_raw), nrow(operator_raw), nrow(qsm_raw),
    nrow(classical_raw), nrow(triplet_distance_raw), nrow(triplet_mti_raw), nrow(diag_context_raw), nrow(diag_perplexity_raw),
    nrow(diag_choice_raw), nrow(representation_alignment), nrow(variant_sensitivity)
  ),
  columns = c(
    ncol(pair_design_raw), ncol(triplet_design), ncol(diagnostic_design), ncol(subspace_raw), ncol(operator_raw), ncol(qsm_raw),
    ncol(classical_raw), ncol(triplet_distance_raw), ncol(triplet_mti_raw), ncol(diag_context_raw), ncol(diag_perplexity_raw),
    ncol(diag_choice_raw), ncol(representation_alignment), ncol(variant_sensitivity)
  )
)

apa_table(overview, caption = "Table 1. Imported data objects.")
Table 1. Imported data objects.
data_object rows columns
pair_design_raw 350 34
triplet_design 66 17
diagnostic_design 220 15
subspace_raw 113288 38
operator_raw 451248 25
qsm_raw 171360 17
classical_raw 7140 11
triplet_distance_raw 3168 12
triplet_mti_raw 3168 13
diag_context_raw 3520 26
diag_perplexity_raw 1760 36
diag_choice_raw 9856 13
representation_alignment 7616 18
variant_sensitivity 26712 16
Show code
write_table(overview, "table_01_imported_data_objects.csv")

6 Prepare human-aligned analysis tables

Show code
pair_behavior <- pair_design_raw %>%
  rename(
    human_target_a = target_a,
    human_target_b = target_b
  ) %>%
  mutate(
    pair_id = if_else(is.na(pair_id), canonical_pair(human_target_a, human_target_b), pair_id),
    human_first = as.character(human_target_a),
    human_second = as.character(human_target_b),
    context = as.character(context),
    human_value = as.numeric(human_value),
    human_n = as.numeric(human_n)
  ) %>%
  filter(both_available %in% c(TRUE, "TRUE", 1, "1"), !is.na(human_value))

human_pair_ids <- pair_behavior %>% distinct(pair_id)

subspace_metric_cols <- c(
  "principal_overlap_trace", "principal_overlap_nuclear", "mean_cosine", "mean_cosine_sq", "min_cosine", "max_cosine",
  "mean_angle_deg", "max_angle_deg", "min_angle_deg", "containment_a_to_b", "containment_b_to_a",
  "containment_asymmetry_a_minus_b", "symmetric_projector_overlap", "projector_chordal_distance",
  "projector_chordal_distance_normalized", "grassmann_geodesic_min_dim", "projector_commutator_fro",
  "projector_commutator_fro_normalized", "density_fidelity_from_projectors", "density_bures_from_projectors",
  "qsm_neutral_a_to_b", "qsm_neutral_b_to_a", "qsm_neutral_asymmetry_a_minus_b"
)

operator_metric_cols <- c(
  "trace_inner", "frobenius_distance", "frobenius_distance_sq", "cosine_hilbert_schmidt",
  "a_in_b_trace_normalized", "b_in_a_trace_normalized", "containment_asymmetry_a_minus_b",
  "symmetric_trace_normalized_overlap", "frobenius_distance_trace_normalized"
)

qsm_metric_cols <- c("qsm_p_b_given_a", "qsm_p_a_given_b", "qsm_asymmetry_a_to_b_minus_b_to_a")

make_subspace_long <- function(df) {
  df %>%
    semi_join(human_pair_ids, by = "pair_id") %>%
    rename(model_target_a = target_a, model_target_b = target_b) %>%
    pivot_longer(cols = any_of(subspace_metric_cols), names_to = "metric_name", values_to = "value") %>%
    mutate(
      metric_source = "pairwise_subspace_metrics",
      object = basis_object,
      state_vector_source = NA_character_,
      state_type = NA_character_
    )
}

make_operator_long <- function(df) {
  df %>%
    semi_join(human_pair_ids, by = "pair_id") %>%
    rename(model_target_a = target_a, model_target_b = target_b, basis_object = object) %>%
    pivot_longer(cols = any_of(operator_metric_cols), names_to = "metric_name", values_to = "value") %>%
    mutate(
      metric_source = "pairwise_operator_density_metrics",
      object = basis_object,
      state_vector_source = NA_character_,
      state_type = NA_character_
    )
}

make_qsm_long <- function(df) {
  df %>%
    semi_join(human_pair_ids, by = "pair_id") %>%
    rename(model_target_a = target_a, model_target_b = target_b) %>%
    pivot_longer(cols = any_of(qsm_metric_cols), names_to = "metric_name", values_to = "value") %>%
    mutate(
      metric_source = "pairwise_qsm_state_metrics",
      object = basis_object
    )
}

make_classical_long <- function(df) {
  df %>%
    semi_join(human_pair_ids, by = "pair_id") %>%
    rename(model_target_a = target_a, model_target_b = target_b) %>%
    pivot_longer(
      cols = any_of(c("cosine", "angular_distance", "euclidean_distance", "dot_product")),
      names_to = "baseline_metric",
      values_to = "value"
    ) %>%
    mutate(
      metric_name = paste0("classical_", vector_source, "_", baseline_metric),
      metric_source = "classical_baselines",
      variant = "classical",
      cluster_method = NA_character_,
      global_k_method = NA_character_,
      local_k_method = NA_character_,
      basis_object = NA_character_,
      object = vector_source,
      state_vector_source = vector_source,
      state_type = "vector"
    )
}

pairwise_metric_values <- bind_rows(
  make_subspace_long(subspace_raw),
  make_operator_long(operator_raw),
  make_qsm_long(qsm_raw),
  make_classical_long(classical_raw)
) %>%
  mutate(
    value = as.numeric(value),
    metric_family = metric_family_from_source(metric_source),
    metric_short = metric_short_label(metric_name),
    variant_label = if_else(metric_source == "classical_baselines", "classical", variant_short(variant)),
    metric_id = paste(
      metric_source, metric_name, coalesce(variant, ""), coalesce(basis_object, ""),
      coalesce(state_vector_source, ""), coalesce(state_type, ""), sep = " | "
    )
  )

join_metrics_to_behavior <- function(human_df, metrics_df = pairwise_metric_values) {
  metrics_df %>%
    inner_join(human_df, by = "pair_id") %>%
    mutate(
      direction_sign = case_when(
        model_target_a == human_first & model_target_b == human_second ~ 1,
        model_target_a == human_second & model_target_b == human_first ~ -1,
        TRUE ~ NA_real_
      ),
      metric_value_directional = orient_asymmetry_to_human_order(metric_name, value, direction_sign),
      metric_value = similarity_orient_value(metric_name, metric_value_directional),
      metric_direction = case_when(
        is_asymmetry_metric(metric_name) ~ "directional_asymmetry",
        is_distance_metric(metric_name) ~ "distance_reversed_to_similarity",
        TRUE ~ "similarity_or_overlap"
      )
    )
}

pairwise_all_human <- join_metrics_to_behavior(pair_behavior)
write_csv(pairwise_all_human, file.path(output_dir, "data", "pairwise_all_human_aligned_long_v7.csv"))

pairwise_source_summary <- pairwise_all_human %>%
  count(hypothesis, metric_family, metric_source, sort = TRUE)

apa_table(
  pairwise_source_summary,
  caption = "Table 2. Human-aligned pairwise metric rows by hypothesis and metric family."
)
Table 2. Human-aligned pairwise metric rows by hypothesis and metric family.
hypothesis metric_family metric_source n
H3_likert_order_asymmetry_H4_triangle_network Operator/density geometry pairwise_operator_density_metrics 103680
H3_likert_order_asymmetry_H4_triangle_network Subspace/projector geometry pairwise_subspace_metrics 66240
H6_diagnosticity_choice_full Operator/density geometry pairwise_operator_density_metrics 38016
H6_diagnosticity_rating Operator/density geometry pairwise_operator_density_metrics 25344
H6_diagnosticity_choice_full Subspace/projector geometry pairwise_subspace_metrics 24288
H1_asymmetry_ipsative Operator/density geometry pairwise_operator_density_metrics 17280
H2_salience Operator/density geometry pairwise_operator_density_metrics 17280
H6_diagnosticity_rating Subspace/projector geometry pairwise_subspace_metrics 16192
H3_likert_order_asymmetry_H4_triangle_network Sequential QSM pairwise_qsm_state_metrics 12960
H1_asymmetry_ipsative Subspace/projector geometry pairwise_subspace_metrics 11040
H2_salience Subspace/projector geometry pairwise_subspace_metrics 11040
H6_diagnosticity_choice_full Sequential QSM pairwise_qsm_state_metrics 4752
H6_diagnosticity_rating Sequential QSM pairwise_qsm_state_metrics 3168
H1_asymmetry_ipsative Sequential QSM pairwise_qsm_state_metrics 2160
H2_salience Sequential QSM pairwise_qsm_state_metrics 2160
H3_likert_order_asymmetry_H4_triangle_network Classical vector baseline classical_baselines 720
H6_diagnosticity_choice_full Classical vector baseline classical_baselines 264
H6_diagnosticity_rating Classical vector baseline classical_baselines 176
H1_asymmetry_ipsative Classical vector baseline classical_baselines 120
H2_salience Classical vector baseline classical_baselines 120
Show code
write_table(pairwise_source_summary, "table_02_pairwise_rows_by_hypothesis_and_metric_family.csv")

7 Compute all hypothesis-level rankings

Show code
# H1 and H2 use directional asymmetry metrics as primary, but cosine baselines are kept for systematic comparison.
h1_human <- pair_behavior %>% filter(hypothesis == "H1_asymmetry_ipsative")
h2_human <- pair_behavior %>% filter(hypothesis == "H2_salience")

h1_all <- join_metrics_to_behavior(h1_human)
h2_all <- join_metrics_to_behavior(h2_human)

rank_group_cols <- c(
  "metric_id", "metric_source", "metric_family", "metric_name", "metric_short", "variant", "variant_label", "cluster_method",
  "global_k_method", "local_k_method", "basis_object", "object", "state_vector_source", "state_type", "metric_direction"
)

h1_rank_all <- cor_by_group(h1_all, rank_group_cols) %>% mutate(analysis_block = "H1 ipsative asymmetry")
h1_rank_directional <- h1_rank_all %>% filter(is_asymmetry_metric(metric_name))
h1_rank_baselines <- h1_rank_all %>% filter(metric_source == "classical_baselines")

h2_rank_all <- cor_by_group(h2_all, rank_group_cols) %>% mutate(analysis_block = "H2 salience")
h2_rank_directional <- h2_rank_all %>% filter(is_asymmetry_metric(metric_name))
h2_rank_baselines <- h2_rank_all %>% filter(metric_source == "classical_baselines")

# H2b: single-concept Likert salience.
# This is concept-level rather than pairwise. Therefore, the relevant predictors are not pairwise cosines,
# but properties of each concept representation: frequency, global dimensionality, and local richness.
salience_metadata_cols <- c(
  "ID", "source_file", "source_row", "raw_ID", "participant_id",
  "attempt_index_within_source", "n_rows_for_participant_in_source"
)

salience_likert_long <- if (nrow(saliency_2_raw) > 0) {
  saliency_2_raw %>%
    select(-any_of(salience_metadata_cols), -ends_with("_rt")) %>%
    pivot_longer(everything(), names_to = "target_raw", values_to = "salience_likert") %>%
    mutate(
      target_id = normalize_salience_target(target_raw),
      salience_likert = as.numeric(salience_likert)
    ) %>%
    filter(!is.na(salience_likert), salience_likert >= 1, salience_likert <= 9)
} else {
  tibble(target_raw = character(), target_id = character(), salience_likert = numeric())
}

salience_likert_summary <- salience_likert_long %>%
  group_by(target_id) %>%
  summarise(
    mean_salience_likert = mean(salience_likert, na.rm = TRUE),
    sd_salience_likert = sd(salience_likert, na.rm = TRUE),
    n_salience_likert = n(),
    se_salience_likert = sd_salience_likert / sqrt(n_salience_likert),
    .groups = "drop"
  )

local_dimensionality_summary <- if (nrow(local_dimensionality_raw) > 0) {
  local_dimensionality_raw %>%
    mutate(
      is_estimable_local = !is.na(rank_effective) & rank_effective >= 1,
      across(any_of(c("gavish_donoho", "horn_wishart", "permutation_pa", "profile_likelihood", "var80", "var90", "var95")), as.numeric)
    ) %>%
    group_by(target_id, cluster_method) %>%
    summarise(
      n_local_candidates = n(),
      n_estimable_local_clusters = sum(is_estimable_local, na.rm = TRUE),
      mean_local_gavish_donoho = mean(gavish_donoho[is_estimable_local], na.rm = TRUE),
      mean_local_horn_wishart = mean(horn_wishart[is_estimable_local], na.rm = TRUE),
      mean_local_permutation_pa = mean(permutation_pa[is_estimable_local], na.rm = TRUE),
      sum_local_gavish_donoho = sum(gavish_donoho[is_estimable_local], na.rm = TRUE),
      sum_local_horn_wishart = sum(horn_wishart[is_estimable_local], na.rm = TRUE),
      sum_local_permutation_pa = sum(permutation_pa[is_estimable_local], na.rm = TRUE),
      .groups = "drop"
    ) %>%
    pivot_wider(
      names_from = cluster_method,
      values_from = c(
        n_local_candidates, n_estimable_local_clusters,
        mean_local_gavish_donoho, mean_local_horn_wishart, mean_local_permutation_pa,
        sum_local_gavish_donoho, sum_local_horn_wishart, sum_local_permutation_pa
      ),
      names_sep = "__"
    )
} else {
  tibble(target_id = character())
}

salience_likert_dimensionality <- salience_likert_summary %>%
  inner_join(global_dimensionality_raw, by = "target_id") %>%
  left_join(local_dimensionality_summary, by = "target_id") %>%
  mutate(
    log10_n_occurrences = log10(n_occurrences),
    salience_z = z_score(mean_salience_likert),
    log10_n_occurrences_z = z_score(log10_n_occurrences)
  )

salience_predictor_cols <- c(
  "log10_n_occurrences", "n_occurrences", "rank_effective", "broken_stick", "gavish_donoho",
  "horn_wishart", "permutation_pa", "marchenko_pastur", "profile_likelihood",
  "individual_var_ge_0.005", "var80", "var90", "var95", "var99",
  "n_estimable_local_clusters__bayesian_gmm", "n_estimable_local_clusters__gmm_bic",
  "mean_local_gavish_donoho__bayesian_gmm", "mean_local_gavish_donoho__gmm_bic",
  "sum_local_gavish_donoho__bayesian_gmm", "sum_local_gavish_donoho__gmm_bic",
  "mean_local_horn_wishart__bayesian_gmm", "mean_local_horn_wishart__gmm_bic",
  "sum_local_horn_wishart__bayesian_gmm", "sum_local_horn_wishart__gmm_bic"
)

salience_predictor_labels <- c(
  log10_n_occurrences = "Log frequency",
  n_occurrences = "Raw frequency",
  rank_effective = "Effective rank",
  broken_stick = "Broken-stick dimensionality",
  gavish_donoho = "Gavish-Donoho dimensionality",
  horn_wishart = "Horn/Wishart dimensionality",
  permutation_pa = "Permutation PA dimensionality",
  marchenko_pastur = "Marchenko-Pastur dimensionality",
  profile_likelihood = "Profile-likelihood dimensionality",
  individual_var_ge_0.005 = "Individual variance ≥ .005",
  var80 = "Dimensions for 80% variance",
  var90 = "Dimensions for 90% variance",
  var95 = "Dimensions for 95% variance",
  var99 = "Dimensions for 99% variance",
  `n_estimable_local_clusters__bayesian_gmm` = "Estimable local clusters, Bayesian GMM",
  `n_estimable_local_clusters__gmm_bic` = "Estimable local clusters, GMM-BIC",
  `mean_local_gavish_donoho__bayesian_gmm` = "Mean local Gavish, Bayesian GMM",
  `mean_local_gavish_donoho__gmm_bic` = "Mean local Gavish, GMM-BIC",
  `sum_local_gavish_donoho__bayesian_gmm` = "Total local Gavish, Bayesian GMM",
  `sum_local_gavish_donoho__gmm_bic` = "Total local Gavish, GMM-BIC",
  `mean_local_horn_wishart__bayesian_gmm` = "Mean local Horn, Bayesian GMM",
  `mean_local_horn_wishart__gmm_bic` = "Mean local Horn, GMM-BIC",
  `sum_local_horn_wishart__bayesian_gmm` = "Total local Horn, Bayesian GMM",
  `sum_local_horn_wishart__gmm_bic` = "Total local Horn, GMM-BIC"
)

h2_likert_predictor_long <- salience_likert_dimensionality %>%
  pivot_longer(
    cols = any_of(salience_predictor_cols),
    names_to = "metric_name",
    values_to = "metric_value"
  ) %>%
  mutate(
    metric_value = as.numeric(metric_value),
    human_value = mean_salience_likert,
    metric_source = "concept_level_dimensionality",
    metric_family = case_when(
      metric_name %in% c("log10_n_occurrences", "n_occurrences") ~ "Corpus frequency baseline",
      str_detect(metric_name, "local|clusters") ~ "Local representational richness",
      TRUE ~ "Global representational dimensionality"
    ),
    metric_short = recode(metric_name, !!!salience_predictor_labels, .default = metric_name),
    variant = NA_character_,
    variant_label = NA_character_,
    cluster_method = NA_character_,
    global_k_method = NA_character_,
    local_k_method = NA_character_,
    basis_object = NA_character_,
    object = "concept-level representation",
    state_vector_source = NA_character_,
    state_type = NA_character_,
    metric_direction = "concept_level_predictor",
    metric_id = paste(metric_source, metric_name, sep = " | ")
  ) %>%
  filter(!is.na(metric_value), is.finite(metric_value), !is.na(human_value))

h2_likert_rank <- cor_by_group(h2_likert_predictor_long, rank_group_cols) %>%
  mutate(analysis_block = "H2b Likert salience")

run_frequency_adjusted_salience_model <- function(predictor_name) {
  d <- salience_likert_dimensionality %>%
    select(target_id, mean_salience_likert, log10_n_occurrences, all_of(predictor_name)) %>%
    rename(predictor = all_of(predictor_name)) %>%
    mutate(
      predictor = as.numeric(predictor),
      salience_z = z_score(mean_salience_likert),
      predictor_z = z_score(predictor),
      log_frequency_z = z_score(log10_n_occurrences)
    ) %>%
    filter(if_all(all_of(c("salience_z", "predictor_z", "log_frequency_z")), ~ !is.na(.x) & is.finite(.x)))

  if (nrow(d) < 6 || n_distinct(d$predictor_z) < 2) {
    return(tibble(
      metric_name = predictor_name,
      n = nrow(d),
      beta_predictor = NA_real_,
      p_predictor = NA_real_,
      beta_log_frequency = NA_real_,
      p_log_frequency = NA_real_,
      r_squared = NA_real_
    ))
  }

  fit <- lm(salience_z ~ predictor_z + log_frequency_z, data = d)
  td <- broom::tidy(fit)
  gl <- broom::glance(fit)
  tibble(
    metric_name = predictor_name,
    n = nrow(d),
    beta_predictor = td$estimate[td$term == "predictor_z"],
    p_predictor = td$p.value[td$term == "predictor_z"],
    beta_log_frequency = td$estimate[td$term == "log_frequency_z"],
    p_log_frequency = td$p.value[td$term == "log_frequency_z"],
    r_squared = gl$r.squared
  )
}

h2_likert_frequency_adjusted <- map_dfr(
  setdiff(intersect(salience_predictor_cols, names(salience_likert_dimensionality)), c("log10_n_occurrences", "n_occurrences")),
  run_frequency_adjusted_salience_model
) %>%
  mutate(
    metric_short = recode(metric_name, !!!salience_predictor_labels, .default = metric_name),
    p_fdr_predictor = p.adjust(p_predictor, method = "BH"),
    abs_beta_predictor = abs(beta_predictor)
  ) %>%
  arrange(p_predictor, desc(abs_beta_predictor))


# H2c: planned salience/similarity condition structure from the thesis annex.
# The annex condition table is entered explicitly because the exported response files do not carry
# this full design metadata. The order here is the theoretical/planned order, not necessarily the
# actual on-screen presentation order in every task file.
annex_salience_conditions <- tibble::tribble(
  ~planned_concept_1, ~planned_concept_2, ~planned_concept_1_es, ~planned_concept_2_es, ~planned_salience_1, ~planned_salience_2, ~planned_similarity, ~annex_condition, ~condition_source,
  "ghana", "luxemburgo", "Ghana", "Luxemburgo", "low", "low", "low", "low_low__low_similarity", "thesis_annex",
  "islas_feroe", "singapur", "Islas Feroe", "Singapur", "low", "low", "low", "low_low__low_similarity", "thesis_annex",
  "burkina_faso", "guayana_francesa", "Burkina Faso", "Guayana Francesa", "low", "low", "low", "low_low__low_similarity", "thesis_annex",
  "mongolia", "bulgaria", "Mongolia", "Bulgaria", "low", "low", "low", "low_low__low_similarity", "thesis_annex",
  "oman", "belice", "Omán", "Belice", "low", "low", "low", "low_low__low_similarity", "thesis_annex",

  "eslovenia", "eslovaquia", "Eslovenia", "Eslovaquia", "low", "low", "high", "low_low__high_similarity", "thesis_annex",
  "zambia", "zimbabue", "Zambia", "Zimbabue", "low", "low", "high", "low_low__high_similarity", "thesis_annex",
  "laos", "camboya", "Laos", "Camboya", "low", "low", "high", "low_low__high_similarity", "thesis_annex",
  "oman", "yemen", "Omán", "Yemen", "low", "low", "high", "low_low__high_similarity", "thesis_annex",
  "turkmenistan", "uzbekistan", "Turkmenistán", "Uzbekistán", "low", "low", "high", "low_low__high_similarity", "thesis_annex",

  "francia", "bolivia", "Francia", "Bolivia", "high", "low", "low", "high_low__low_similarity", "thesis_annex",
  "estados_unidos", "belice", "Estados Unidos", "Belice", "high", "low", "low", "high_low__low_similarity", "thesis_annex",
  "china", "luxemburgo", "China", "Luxemburgo", "high", "low", "low", "high_low__low_similarity", "thesis_annex",
  "mexico", "laos", "México", "Laos", "high", "low", "low", "high_low__low_similarity", "thesis_annex",
  "rusia", "burkina_faso", "Rusia", "Burkina Faso", "high", "low", "low", "high_low__low_similarity", "thesis_annex",

  "francia", "belgica", "Francia", "Bélgica", "high", "low", "high", "high_low__high_similarity", "thesis_annex",
  "espana", "andorra", "España", "Andorra", "high", "low", "high", "high_low__high_similarity", "thesis_annex",
  "china", "singapur", "China", "Singapur", "high", "low", "high", "high_low__high_similarity", "thesis_annex",
  "rusia", "bielorrusia", "Rusia", "Bielorrusia", "high", "low", "high", "high_low__high_similarity", "thesis_annex",
  "argentina", "paraguay", "Argentina", "Paraguay", "high", "low", "high", "high_low__high_similarity", "thesis_annex",

  "francia", "brasil", "Francia", "Brasil", "high", "high", "low", "high_high__low_similarity", "thesis_annex",
  "alemania", "canada", "Alemania", "Canadá", "high", "high", "low", "high_high__low_similarity", "thesis_annex",
  "italia", "japon", "Italia", "Japón", "high", "high", "low", "high_high__low_similarity", "thesis_annex",
  "marruecos", "argentina", "Marruecos", "Argentina", "high", "high", "low", "high_high__low_similarity", "thesis_annex",
  "india", "mexico", "India", "México", "high", "high", "low", "high_high__low_similarity", "thesis_annex",

  "italia", "espana", "Italia", "España", "high", "high", "high", "high_high__high_similarity", "thesis_annex",
  "reino_unido", "estados_unidos", "Reino Unido", "Estados Unidos", "high", "high", "high", "high_high__high_similarity", "thesis_annex",
  "suecia", "noruega", "Suecia", "Noruega", "high", "high", "high", "high_high__high_similarity", "thesis_annex",
  "brasil", "portugal", "Brasil", "Portugal", "high", "high", "high", "high_high__high_similarity", "thesis_annex",
  "mexico", "colombia", "México", "Colombia", "high", "high", "high", "high_high__high_similarity", "thesis_annex_not_observed_in_current_human_pair_file",
  "ecuador", "peru", "Ecuador", "Perú", "high", "high", "high", "high_high__high_similarity", "observed_human_file_replacement_or_extra"
) %>%
  mutate(
    pair_id = canonical_pair(planned_concept_1, planned_concept_2),
    planned_salience_pair = paste(planned_salience_1, planned_salience_2, sep = "_"),
    planned_similarity = factor(planned_similarity, levels = c("low", "high")),
    planned_salience_pair = factor(planned_salience_pair, levels = c("low_low", "high_low", "high_high")),
    annex_condition = factor(
      annex_condition,
      levels = c(
        "low_low__low_similarity", "low_low__high_similarity",
        "high_low__low_similarity", "high_low__high_similarity",
        "high_high__low_similarity", "high_high__high_similarity"
      )
    )
  )

annex_condition_audit <- annex_salience_conditions %>%
  left_join(
    pair_behavior %>% distinct(pair_id) %>% mutate(observed_in_human_pair_design = TRUE),
    by = "pair_id"
  ) %>%
  mutate(observed_in_human_pair_design = coalesce(observed_in_human_pair_design, FALSE))

pair_behavior_with_conditions <- pair_behavior %>%
  left_join(annex_salience_conditions, by = "pair_id") %>%
  mutate(
    human_first_planned_salience = case_when(
      human_first == planned_concept_1 ~ planned_salience_1,
      human_first == planned_concept_2 ~ planned_salience_2,
      TRUE ~ NA_character_
    ),
    human_second_planned_salience = case_when(
      human_second == planned_concept_1 ~ planned_salience_1,
      human_second == planned_concept_2 ~ planned_salience_2,
      TRUE ~ NA_character_
    ),
    presentation_salience_pattern = if_else(
      !is.na(human_first_planned_salience) & !is.na(human_second_planned_salience),
      paste(human_first_planned_salience, human_second_planned_salience, sep = "_"),
      NA_character_
    ),
    presentation_salience_pattern = factor(
      presentation_salience_pattern,
      levels = c("low_low", "low_high", "high_low", "high_high")
    ),
    salience_contrast_type = case_when(
      presentation_salience_pattern %in% c("high_low", "low_high") ~ "mixed_salience",
      presentation_salience_pattern == "high_high" ~ "both_high",
      presentation_salience_pattern == "low_low" ~ "both_low",
      TRUE ~ NA_character_
    ),
    prop_choose_high_salience = case_when(
      hypothesis == "H2_salience" & presentation_salience_pattern == "high_low" ~ human_value,
      hypothesis == "H2_salience" & presentation_salience_pattern == "low_high" ~ 1 - human_value,
      TRUE ~ NA_real_
    ),
    distance_from_chance = abs(human_value - .5)
  )

annex_concept_salience_labels <- annex_salience_conditions %>%
  select(pair_id, planned_concept_1, planned_concept_2, planned_salience_1, planned_salience_2) %>%
  pivot_longer(
    cols = c(planned_concept_1, planned_concept_2),
    names_to = "concept_position",
    values_to = "target_id"
  ) %>%
  mutate(
    planned_salience_label = if_else(concept_position == "planned_concept_1", planned_salience_1, planned_salience_2)
  ) %>%
  distinct(target_id, planned_salience_label) %>%
  group_by(target_id) %>%
  summarise(
    n_high_labels = sum(planned_salience_label == "high"),
    n_low_labels = sum(planned_salience_label == "low"),
    planned_salience_label = case_when(
      n_high_labels > 0 & n_low_labels == 0 ~ "high",
      n_low_labels > 0 & n_high_labels == 0 ~ "low",
      n_high_labels > 0 & n_low_labels > 0 ~ "mixed_or_inconsistent",
      TRUE ~ NA_character_
    ),
    .groups = "drop"
  )

group_difference <- function(data, value_col, group_col, high_label = "high", low_label = "low") {
  d <- data %>%
    select(value = all_of(value_col), group = all_of(group_col)) %>%
    filter(!is.na(value), is.finite(value), group %in% c(high_label, low_label)) %>%
    mutate(group = as.character(group))

  if (nrow(d) < 4 || n_distinct(d$group) < 2) {
    return(tibble(
      outcome = value_col,
      n_high = sum(d$group == high_label),
      n_low = sum(d$group == low_label),
      mean_high = mean(d$value[d$group == high_label], na.rm = TRUE),
      mean_low = mean(d$value[d$group == low_label], na.rm = TRUE),
      difference_high_minus_low = NA_real_,
      p.value = NA_real_
    ))
  }

  tt <- suppressWarnings(t.test(value ~ group, data = d))
  tibble(
    outcome = value_col,
    n_high = sum(d$group == high_label),
    n_low = sum(d$group == low_label),
    mean_high = mean(d$value[d$group == high_label], na.rm = TRUE),
    mean_low = mean(d$value[d$group == low_label], na.rm = TRUE),
    difference_high_minus_low = mean_high - mean_low,
    p.value = tt$p.value
  )
}

h2c_human_condition_summary <- pair_behavior_with_conditions %>%
  filter(hypothesis %in% c("H1_asymmetry_ipsative", "H2_salience", "H3_likert_order_asymmetry_H4_triangle_network")) %>%
  filter(!is.na(annex_condition)) %>%
  group_by(hypothesis, annex_condition, presentation_salience_pattern, planned_similarity) %>%
  summarise(
    n_items = n_distinct(pair_id),
    mean_human_value = mean(human_value, na.rm = TRUE),
    mean_abs_deviation_from_chance = mean(distance_from_chance, na.rm = TRUE),
    mean_prop_choose_high_salience = mean(prop_choose_high_salience, na.rm = TRUE),
    .groups = "drop"
  )

h2c_likert_condition_dimensionality <- salience_likert_dimensionality %>%
  inner_join(annex_concept_salience_labels, by = "target_id") %>%
  filter(planned_salience_label %in% c("high", "low"))

h2c_concept_condition_summary <- h2c_likert_condition_dimensionality %>%
  group_by(planned_salience_label) %>%
  summarise(
    n_concepts = n(),
    mean_likert_salience = mean(mean_salience_likert, na.rm = TRUE),
    mean_log10_frequency = mean(log10_n_occurrences, na.rm = TRUE),
    mean_effective_rank = mean(rank_effective, na.rm = TRUE),
    mean_horn_dimensionality = mean(horn_wishart, na.rm = TRUE),
    mean_gavish_dimensionality = mean(gavish_donoho, na.rm = TRUE),
    mean_permutation_dimensionality = mean(permutation_pa, na.rm = TRUE),
    .groups = "drop"
  )

h2c_concept_condition_tests <- map_dfr(
  intersect(c("mean_salience_likert", "log10_n_occurrences", "rank_effective", "horn_wishart", "gavish_donoho", "permutation_pa", "profile_likelihood"), names(h2c_likert_condition_dimensionality)),
  ~ group_difference(h2c_likert_condition_dimensionality, .x, "planned_salience_label")
) %>%
  mutate(
    p_fdr = p.adjust(p.value, method = "BH"),
    outcome_label = recode(outcome, !!!salience_predictor_labels, mean_salience_likert = "Mean Likert salience", log10_n_occurrences = "Log frequency", .default = outcome)
  ) %>%
  arrange(p.value)

h2c_similarity_condition_human <- pair_behavior %>%
  filter(hypothesis == "H3_likert_order_asymmetry_H4_triangle_network", response_type == "rating_1_9") %>%
  group_by(pair_id) %>%
  summarise(
    human_value = mean(human_value, na.rm = TRUE),
    human_sd = sd(human_value, na.rm = TRUE),
    human_n = sum(human_n, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  inner_join(annex_salience_conditions, by = "pair_id")

h2c_similarity_condition_summary <- h2c_similarity_condition_human %>%
  group_by(planned_similarity, planned_salience_pair) %>%
  summarise(
    n_pairs = n_distinct(pair_id),
    mean_human_similarity = mean(human_value, na.rm = TRUE),
    sd_human_similarity = sd(human_value, na.rm = TRUE),
    .groups = "drop"
  )

h2c_similarity_condition_lm <- if (nrow(h2c_similarity_condition_human) >= 6) {
  broom::tidy(lm(human_value ~ planned_similarity + planned_salience_pair, data = h2c_similarity_condition_human))
} else {
  tibble(term = character(), estimate = numeric(), std.error = numeric(), statistic = numeric(), p.value = numeric())
}

h2c_pairwise_condition_metric_tests <- pairwise_metric_values %>%
  inner_join(annex_salience_conditions %>% select(pair_id, planned_similarity, annex_condition, planned_salience_pair), by = "pair_id") %>%
  filter(!is_asymmetry_metric(metric_name)) %>%
  mutate(metric_value = similarity_orient_value(metric_name, value)) %>%
  filter(!is.na(metric_value), is.finite(metric_value)) %>%
  group_by(metric_id, metric_source, metric_family, metric_name, metric_short, variant, variant_label, basis_object, object, state_vector_source, state_type) %>%
  group_modify(~ group_difference(.x, "metric_value", "planned_similarity")) %>%
  ungroup() %>%
  mutate(
    p_fdr = p.adjust(p.value, method = "BH"),
    abs_difference = abs(difference_high_minus_low)
  ) %>%
  arrange(desc(abs_difference), p.value)

# H3a: symmetric pairwise similarity, order-averaged by pair.
h3_similarity_human <- pair_behavior %>%
  filter(hypothesis == "H3_likert_order_asymmetry_H4_triangle_network", response_type == "rating_1_9") %>%
  group_by(pair_id) %>%
  summarise(
    human_value = mean(human_value, na.rm = TRUE),
    human_sd = sd(human_value, na.rm = TRUE),
    human_n = sum(human_n, na.rm = TRUE),
    human_first = first(human_first),
    human_second = first(human_second),
    hypothesis = "H3_rating_similarity_order_averaged",
    response_type = "rating_1_9_order_averaged",
    .groups = "drop"
  )

h3_similarity_metrics <- pairwise_metric_values %>%
  filter(!is_asymmetry_metric(metric_name)) %>%
  distinct(metric_id, pair_id, .keep_all = TRUE) %>%
  inner_join(h3_similarity_human, by = "pair_id") %>%
  mutate(
    metric_value = similarity_orient_value(metric_name, value),
    metric_direction = if_else(is_distance_metric(metric_name), "distance_reversed_to_similarity", "similarity_or_overlap")
  )

h3_rating_rank <- cor_by_group(h3_similarity_metrics, rank_group_cols) %>%
  mutate(analysis_block = "H3 Likert similarity")

# H3b: human order asymmetry, compared only with directional asymmetry metrics.
h3_order_human <- pair_behavior %>%
  filter(hypothesis == "H3_likert_order_asymmetry_H4_triangle_network", response_type == "rating_1_9") %>%
  mutate(
    order_label = case_when(
      str_detect(coalesce(direction_version, ""), "direct|a_direct") ~ "direct",
      str_detect(coalesce(direction_version, ""), "reverse|b_reverse") ~ "reverse",
      TRUE ~ as.character(direction_version)
    )
  ) %>%
  filter(order_label %in% c("direct", "reverse")) %>%
  group_by(pair_id) %>%
  summarise(
    direct_mean = mean(human_value[order_label == "direct"], na.rm = TRUE),
    reverse_mean = mean(human_value[order_label == "reverse"], na.rm = TRUE),
    human_first = first(human_first),
    human_second = first(human_second),
    n_orders = n_distinct(order_label),
    .groups = "drop"
  ) %>%
  filter(n_orders == 2, is.finite(direct_mean), is.finite(reverse_mean)) %>%
  mutate(
    human_order_delta = direct_mean - reverse_mean,
    human_value = human_order_delta,
    hypothesis = "H3_order_asymmetry"
  )

h3_order_metrics <- join_metrics_to_behavior(h3_order_human, pairwise_metric_values) %>%
  filter(is_asymmetry_metric(metric_name)) %>%
  distinct(metric_id, pair_id, .keep_all = TRUE)

h3_order_rank <- cor_by_group(h3_order_metrics, rank_group_cols, y = "human_order_delta") %>%
  mutate(analysis_block = "H3 order asymmetry")

h3_order_test <- broom::tidy(t.test(h3_order_human$human_order_delta, mu = 0)) %>%
  transmute(
    n = nrow(h3_order_human),
    mean_delta = mean(h3_order_human$human_order_delta, na.rm = TRUE),
    median_abs_delta = median(abs(h3_order_human$human_order_delta), na.rm = TRUE),
    statistic,
    df = parameter,
    p.value
  )

# H4: triplet residuals from order-averaged human ratings.
h3_pair_similarity <- h3_similarity_human %>%
  left_join(
    pair_behavior %>%
      filter(hypothesis == "H3_likert_order_asymmetry_H4_triangle_network", response_type == "rating_1_9") %>%
      group_by(pair_id) %>%
      summarise(
        scale_min = suppressWarnings(min(human_scale_min, na.rm = TRUE)),
        scale_max = suppressWarnings(max(human_scale_max, na.rm = TRUE)),
        .groups = "drop"
      ),
    by = "pair_id"
  ) %>%
  mutate(
    scale_min = if_else(is.finite(scale_min), scale_min, 1),
    scale_max = if_else(is.finite(scale_max), scale_max, 9),
    human_similarity = (human_value - scale_min) / (scale_max - scale_min),
    human_distance = 1 - human_similarity
  )

human_triplets <- triplet_design %>%
  left_join(h3_pair_similarity %>% select(pair_id, sim_endpoint = human_similarity, d_endpoint = human_distance),
            by = c("endpoint_pair_id" = "pair_id")) %>%
  left_join(h3_pair_similarity %>% select(pair_id, sim_leg_1 = human_similarity, d_leg_1 = human_distance),
            by = c("leg_pair_1_id" = "pair_id")) %>%
  left_join(h3_pair_similarity %>% select(pair_id, sim_leg_2 = human_similarity, d_leg_2 = human_distance),
            by = c("leg_pair_2_id" = "pair_id")) %>%
  mutate(
    human_triangle_residual = d_endpoint - (d_leg_1 + d_leg_2),
    human_triangle_violation = human_triangle_residual > 0,
    human_mti_residual = sim_endpoint - (sim_leg_1 * sim_leg_2),
    human_mti_lower_than_product = human_mti_residual < 0,
    human_mti_higher_than_product = human_mti_residual > 0
  )

human_triplet_summary <- human_triplets %>%
  summarise(
    n_contrasts = n(),
    n_unordered_triplets = n_distinct(triplet_unordered_id),
    additive_violations = sum(human_triangle_violation, na.rm = TRUE),
    mti_lower_than_product = sum(human_mti_lower_than_product, na.rm = TRUE),
    mean_triangle_residual = mean(human_triangle_residual, na.rm = TRUE),
    mean_mti_residual = mean(human_mti_residual, na.rm = TRUE)
  )

triplet_distance_human <- triplet_distance_raw %>%
  inner_join(human_triplets %>% select(triplet_id, human_triangle_residual, human_triangle_violation), by = "triplet_id")

h4_distance_rank <- cor_by_group(
  triplet_distance_human,
  group_cols = c("variant", "basis_object", "distance_metric"),
  x = "triangle_residual_ac_minus_ab_bc",
  y = "human_triangle_residual"
) %>%
  mutate(
    analysis_block = "H4 additive triangle",
    metric_source = "triplet_distance_violations",
    metric_family = "Triplet distance geometry",
    metric_name = distance_metric,
    metric_short = metric_short_label(distance_metric),
    variant_label = variant_short(variant)
  )

triplet_mti_human <- triplet_mti_raw %>%
  inner_join(
    human_triplets %>% select(triplet_id, human_mti_residual, human_mti_lower_than_product, human_mti_higher_than_product),
    by = "triplet_id"
  )

h4_mti_rank <- cor_by_group(
  triplet_mti_human,
  group_cols = c("variant", "basis_object", "similarity_metric"),
  x = "mti_residual_sac_minus_sab_sbc",
  y = "human_mti_residual"
) %>%
  mutate(
    analysis_block = "H4 multiplicative triangle",
    metric_source = "triplet_mti_violations",
    metric_family = "Triplet similarity geometry",
    metric_name = similarity_metric,
    metric_short = metric_short_label(similarity_metric),
    variant_label = variant_short(variant)
  )

mti_classification <- triplet_mti_human %>%
  group_by(variant, basis_object, similarity_metric) %>%
  summarise(
    n = n(),
    true_positive = sum(mti_lower_than_product & human_mti_lower_than_product, na.rm = TRUE),
    false_positive = sum(mti_lower_than_product & !human_mti_lower_than_product, na.rm = TRUE),
    false_negative = sum(!mti_lower_than_product & human_mti_lower_than_product, na.rm = TRUE),
    true_negative = sum(!mti_lower_than_product & !human_mti_lower_than_product, na.rm = TRUE),
    accuracy = mean(mti_lower_than_product == human_mti_lower_than_product, na.rm = TRUE),
    precision = true_positive / pmax(true_positive + false_positive, 1),
    recall = true_positive / pmax(true_positive + false_negative, 1),
    f1 = if_else(precision + recall > 0, 2 * precision * recall / (precision + recall), 0),
    .groups = "drop"
  ) %>%
  arrange(desc(f1), desc(accuracy))

# H6 static pairwise metrics.
h6_static <- pair_behavior %>% filter(str_detect(hypothesis, "H6_diagnosticity"))
h6_static_metrics <- join_metrics_to_behavior(h6_static, pairwise_metric_values)

h6_static_rank <- cor_by_group(
  h6_static_metrics,
  group_cols = c("hypothesis", rank_group_cols),
  x = "metric_value",
  y = "human_value"
) %>%
  group_by(hypothesis) %>%
  mutate(p_fdr_within_hypothesis = p.adjust(p.value, method = "BH")) %>%
  ungroup() %>%
  mutate(analysis_block = paste("H6 static", hypothesis))

# H6 contextualized metrics.
diag_human_rows <- pair_behavior %>%
  filter(str_detect(hypothesis, "H6_diagnosticity")) %>%
  transmute(
    hypothesis,
    response_type,
    task_family,
    initial = human_first,
    target = human_second,
    context,
    human_value,
    human_n,
    item_id,
    pair_id
  ) %>%
  filter(!is.na(context), !is.na(initial), !is.na(target))

context_metric_cols <- c(
  "contextualized_vector_cosine", "contextualized_vector_dot",
  "density_hs_inner", "density_hs_cosine", "density_fidelity",
  "density_bures_distance", "density_trace_distance", "density_frobenius_distance"
)

h6_context_long <- diag_context_raw %>%
  pivot_longer(cols = any_of(context_metric_cols), names_to = "metric_name", values_to = "value") %>%
  filter(!is.na(value), status == "ok") %>%
  mutate(
    metric_value = similarity_orient_value(metric_name, value),
    metric_source = "diagnostic_contextualized_similarity",
    metric_family = case_when(
      metric_family == "contextualized_vector" ~ "Contextualized vector",
      metric_family == "luders_contextualized_density" ~ "Lüders contextualized density",
      TRUE ~ as.character(metric_family)
    ),
    metric_short = metric_short_label(metric_name),
    metric_id = paste(metric_family, metric_name, variant, basis_object, density_object, state_vector_source, sep = " | "),
    variant_label = variant_short(variant)
  ) %>%
  inner_join(diag_human_rows, by = c("initial", "context", "target"))

h6_context_rank <- cor_by_group(
  h6_context_long,
  group_cols = c(
    "hypothesis", "metric_id", "metric_source", "metric_family", "metric_name", "metric_short", "variant",
    "variant_label", "cluster_method", "global_k_method", "local_k_method", "basis_object", "density_object", "state_vector_source"
  ),
  x = "metric_value",
  y = "human_value"
) %>%
  group_by(hypothesis) %>%
  mutate(p_fdr_within_hypothesis = p.adjust(p.value, method = "BH")) %>%
  ungroup() %>%
  mutate(analysis_block = paste("H6 contextualized", hypothesis))

# H6 perplexity-inhibited metrics.
perplexity_metric_cols <- c(
  "baseline_projector_containment_initial_to_target",
  "perplexity_residual_density_to_target_containment",
  "perplexity_residual_projector_to_target_containment",
  "delta_residual_density_containment_minus_baseline",
  "delta_residual_projector_containment_minus_baseline",
  "perplexity_density_hs_inner",
  "perplexity_density_hs_cosine",
  "perplexity_density_frobenius_distance",
  "perplexity_density_fidelity",
  "perplexity_density_bures_distance",
  "perplexity_density_trace_distance"
)

h6_perplexity_long <- diag_perplexity_raw %>%
  mutate(context = str_remove(condition, "^ctx-")) %>%
  pivot_longer(cols = any_of(perplexity_metric_cols), names_to = "metric_name", values_to = "value") %>%
  filter(!is.na(value)) %>%
  mutate(
    metric_value = similarity_orient_value(metric_name, value),
    metric_source = "diagnostic_perplexity_metrics",
    metric_family = "Perplexity-inhibited diagnostic",
    metric_short = metric_short_label(metric_name),
    metric_id = paste(metric_family, metric_name, variant, basis_object, density_object, sep = " | "),
    variant_label = variant_short(variant)
  ) %>%
  inner_join(diag_human_rows, by = c("initial", "context", "target"))

h6_perplexity_rank <- cor_by_group(
  h6_perplexity_long,
  group_cols = c(
    "hypothesis", "metric_id", "metric_source", "metric_family", "metric_name", "metric_short", "variant",
    "variant_label", "cluster_method", "global_k_method", "local_k_method", "basis_object", "density_object"
  ),
  x = "metric_value",
  y = "human_value"
) %>%
  group_by(hypothesis) %>%
  mutate(p_fdr_within_hypothesis = p.adjust(p.value, method = "BH")) %>%
  ungroup() %>%
  mutate(analysis_block = paste("H6 perplexity", hypothesis))

# Diagnostic choice predictions: compare predicted pairwise winner with the human winner.
# The prediction table contains pairwise target choices; the human option values come from
# H6 choice-full rows, where human_value is the proportion selecting each option.
human_option_values <- pair_behavior %>%
  filter(hypothesis == "H6_diagnosticity_choice_full") %>%
  transmute(
    initial = human_first,
    context,
    target = human_second,
    human_value
  ) %>%
  group_by(initial, context, target) %>%
  summarise(human_value = mean(human_value, na.rm = TRUE), .groups = "drop")

h6_choice_prediction_human <- diag_choice_raw %>%
  mutate(
    context = str_remove(condition, "^ctx-"),
    diagnostic_choice_set = case_when(
      str_detect(trial_id, "choice_candidates_only") ~ "choice_candidates_only",
      str_detect(trial_id, "choice_full_with_context") ~ "choice_full_with_context",
      TRUE ~ "unknown"
    ),
    hypothesis = "H6_diagnosticity_choice_full"
  ) %>%
  left_join(
    human_option_values %>% rename(target_a = target, human_value_a = human_value),
    by = c("initial", "context", "target_a")
  ) %>%
  left_join(
    human_option_values %>% rename(target_b = target, human_value_b = human_value),
    by = c("initial", "context", "target_b")
  ) %>%
  filter(!is.na(human_value_a), !is.na(human_value_b)) %>%
  mutate(
    human_preferred_choice = if_else(human_value_a >= human_value_b, target_a, target_b),
    correct = predicted_choice == human_preferred_choice,
    metric_source = source_table,
    metric_short = metric_short_label(metric),
    metric_family_label = case_when(
      metric_family == "contextualized_vector" ~ "Contextualized vector",
      metric_family == "luders_contextualized_density" ~ "Lüders contextualized density",
      metric_family == "perplexity" ~ "Perplexity-inhibited diagnostic",
      TRUE ~ as.character(metric_family)
    )
  )

h6_choice_accuracy <- h6_choice_prediction_human %>%
  group_by(hypothesis, diagnostic_choice_set, metric_family_label, metric, variant) %>%
  summarise(
    n = n(),
    accuracy = mean(correct, na.rm = TRUE),
    mean_abs_preference = mean(abs(preference_score_a_minus_b), na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(accuracy), desc(mean_abs_preference))

# Representation alignment and variant sensitivity.
alignment_summary <- representation_alignment %>%
  group_by(object_a, object_b) %>%
  summarise(
    n = n(),
    mean_hs_cosine = mean(cosine_hilbert_schmidt, na.rm = TRUE),
    sd_hs_cosine = sd(cosine_hilbert_schmidt, na.rm = TRUE),
    mean_trace_overlap = mean(symmetric_trace_normalized_overlap, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(mean_hs_cosine))

parse_variant <- function(x, field) {
  m <- str_match(x, "clust-(.*?)__g-(.*?)__l-(.*)$")
  case_when(
    field == "cluster" ~ m[, 2],
    field == "global" ~ m[, 3],
    field == "local" ~ m[, 4],
    TRUE ~ NA_character_
  )
}

variant_sensitivity_labeled <- variant_sensitivity %>%
  mutate(
    cluster_a = parse_variant(variant_a, "cluster"),
    cluster_b = parse_variant(variant_b, "cluster"),
    global_a = parse_variant(variant_a, "global"),
    global_b = parse_variant(variant_b, "global"),
    local_a = parse_variant(variant_a, "local"),
    local_b = parse_variant(variant_b, "local"),
    changed_cluster = cluster_a != cluster_b,
    changed_global = global_a != global_b,
    changed_local = local_a != local_b,
    comparison_type = case_when(
      changed_cluster & !changed_global & !changed_local ~ "Clustering only",
      !changed_cluster & (changed_global | changed_local) ~ "Dimensionality only",
      changed_cluster & (changed_global | changed_local) ~ "Both clustering and dimensionality",
      TRUE ~ "No detected change"
    )
  )

variant_sensitivity_summary <- variant_sensitivity_labeled %>%
  group_by(object, comparison_type) %>%
  summarise(
    n = n(),
    mean_hs_cosine = mean(cosine_hilbert_schmidt, na.rm = TRUE),
    sd_hs_cosine = sd(cosine_hilbert_schmidt, na.rm = TRUE),
    mean_frobenius_distance = mean(frobenius_distance, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(object, comparison_type)

# Combined summaries.
rank_for_summary <- bind_rows(
  h1_rank_directional,
  h2_rank_directional,
  h2_likert_rank,
  h3_rating_rank,
  h3_order_rank,
  h4_distance_rank %>% rename(metric_id = distance_metric),
  h4_mti_rank %>% rename(metric_id = similarity_metric),
  h6_static_rank,
  h6_context_rank,
  h6_perplexity_rank
)

best_predictor_by_block <- bind_rows(
  h1_rank_directional %>% slice_head(n = 1),
  h2_rank_directional %>% slice_head(n = 1),
  h2_likert_rank %>% slice_head(n = 1),
  h3_rating_rank %>% slice_head(n = 1),
  h3_order_rank %>% slice_head(n = 1),
  h4_distance_rank %>% slice_head(n = 1),
  h4_mti_rank %>% slice_head(n = 1),
  h6_static_rank %>% group_by(hypothesis) %>% slice_head(n = 1) %>% ungroup(),
  h6_context_rank %>% group_by(hypothesis) %>% slice_head(n = 1) %>% ungroup(),
  h6_perplexity_rank %>% group_by(hypothesis) %>% slice_head(n = 1) %>% ungroup()
) %>%
  mutate(
    metric_family = coalesce(metric_family, metric_family_from_source(metric_source)),
    display_block = coalesce(analysis_block, hypothesis),
    display_metric = metric_short_label(metric_name)
  )

cosine_comparisons <- bind_rows(
  compare_quantum_vs_cosine(h1_rank_all) %>% mutate(analysis_block = "H1 ipsative asymmetry"),
  compare_quantum_vs_cosine(h2_rank_all) %>% mutate(analysis_block = "H2 salience"),
  compare_quantum_vs_cosine(h3_rating_rank) %>% mutate(analysis_block = "H3 Likert similarity"),
  compare_quantum_vs_cosine(h6_static_rank %>% filter(hypothesis == "H6_diagnosticity_choice_full")) %>% mutate(analysis_block = "H6 choice full"),
  compare_quantum_vs_cosine(h6_static_rank %>% filter(hypothesis == "H6_diagnosticity_rating")) %>% mutate(analysis_block = "H6 rating")
)

# Export full tables.
write_table(h1_rank_all, "h1_all_metric_rankings.csv")
write_table(h1_rank_directional, "h1_directional_metric_rankings.csv")
write_table(h2_rank_all, "h2_all_metric_rankings.csv")
write_table(h2_rank_directional, "h2_directional_metric_rankings.csv")
write_table(h2_likert_rank, "h2b_likert_salience_dimensionality_correlations.csv")
write_table(h2_likert_frequency_adjusted, "h2b_likert_salience_frequency_adjusted_models.csv")
write_table(h3_rating_rank, "h3_rating_similarity_metric_rankings.csv")
write_table(h3_order_rank, "h3_order_asymmetry_metric_rankings.csv")
write_table(h3_order_test, "h3_human_order_asymmetry_test.csv")
write_table(human_triplets, "h4_human_triplet_residuals.csv")
write_table(human_triplet_summary, "h4_human_triplet_summary.csv")
write_table(h4_distance_rank, "h4_additive_triangle_metric_rankings.csv")
write_table(h4_mti_rank, "h4_mti_metric_rankings.csv")
write_table(mti_classification, "h4_mti_classification_performance.csv")
write_table(h6_static_rank, "h6_static_pairwise_metric_rankings.csv")
write_table(h6_context_rank, "h6_contextualized_metric_rankings.csv")
write_table(h6_perplexity_rank, "h6_perplexity_metric_rankings.csv")
write_table(h6_choice_accuracy, "h6_diagnostic_choice_prediction_accuracy.csv")
write_table(alignment_summary, "representation_alignment_summary.csv")
write_table(variant_sensitivity_summary, "variant_sensitivity_summary.csv")
write_table(best_predictor_by_block, "best_predictor_by_analysis_block.csv")
write_table(cosine_comparisons, "cosine_vs_quantum_comparisons.csv")

8 Executive summary

The executive summary should be read as a bridge between statistical performance and theoretical interpretation. A high correlation is not enough by itself; the relevant question is whether the winning metric corresponds to the psychological operation hypothesized for that task. For example, if centroid cosine wins in Likert similarity, that supports an average-vector account of explicit similarity. If containment asymmetry wins in ipsative tasks, that supports the subspace account of directional conceptual inclusion. The same number, in the wrong theoretical family, would mean something quite different.

Show code
summary_table <- best_predictor_by_block %>%
  transmute(
    `Analysis block` = display_block,
    `Best metric family` = metric_family,
    `Best metric` = display_metric,
    `Object / representation` = coalesce(basis_object, object, density_object, state_type, ""),
    `Variant` = str_trunc(coalesce(variant_label, variant_short(variant), ""), 50),
    n,
    `Spearman rho` = apa_r(estimate),
    p = apa_p(p.value),
    q = apa_p(coalesce(p_fdr_within_hypothesis, p_fdr))
  )

apa_table(summary_table, caption = "Table 3. Best computational predictor by analysis block.")
Table 3. Best computational predictor by analysis block.
Analysis block Best metric family Best metric Object / representation Variant n Spearman rho p q
H1 ipsative asymmetry Subspace/projector geometry Containment asymmetry S_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 30 .90 < .001 < .001
H2 salience Subspace/projector geometry Containment asymmetry S_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 30 −.90 < .001 < .001
H2b Likert salience Corpus frequency baseline log10 n occurrences concept-level representation 44 .89 < .001 < .001
H3 Likert similarity Classical vector baseline Angular distance, reversed contour_centroid classical 89 .51 < .001 < .001
H3 order asymmetry Operator/density geometry Containment asymmetry P_concat_direct bayesian_gmm | g=gavish_donoho | l=gavish_donoho 89 .32 .002 .029
H4 additive triangle Triplet distance geometry Bures distance, reversed S_global bayesian_gmm | g=horn_wishart | l=horn_wishart 66 .49 < .001 < .001
H4 multiplicative triangle Triplet similarity geometry Density fidelity S_global bayesian_gmm | g=profile_likelihood | l=profile... 66 .55 < .001 < .001
H6 static H6_diagnosticity_choice_full Operator/density geometry frobenius distance rho_fit_global_density gmm_bic | g=profile_likelihood | l=profile_like... 66 .40 < .001 .235
H6 static H6_diagnosticity_rating Operator/density geometry Containment asymmetry rho_freq_density gmm_bic | g=gavish_donoho | l=gavish_donoho 44 −.69 < .001 < .001
H6 contextualized H6_diagnosticity_choice_full Lüders contextualized density Contextual density HS inner S_global bayesian_gmm | g=profile_likelihood | l=profile... 212 .36 < .001 < .001
H6 perplexity H6_diagnosticity_choice_full Perplexity-inhibited diagnostic perplexity residual projector to target containment S_global bayesian_gmm | g=horn_wishart | l=horn_wishart 212 −.26 < .001 .007
Show code
write_table(summary_table, "table_03_executive_best_predictor_by_block_apa.csv")

The strongest pattern in the report is the contrast between general similarity, directional conceptual structure, and concept-level representational availability. Likert similarity is where the classical contour-centroid cosine is difficult to beat. Ipsative asymmetry and ipsative salience are different: there, containment asymmetry between subspaces is the dominant predictor. Single-concept Likert salience is different again: it is tested against frequency and dimensionality because the dependent variable refers to a concept considered on its own. Apparently, the mind did not ask permission before becoming asymmetric, scalar, and frequency-sensitive all at once.

Show code
cosine_table <- cosine_comparisons %>%
  transmute(
    `Analysis block` = analysis_block,
    `Comparison family` = comparison_family,
    `Metric` = metric_short_label(metric_name),
    `Metric source` = metric_source,
    `Representation` = coalesce(basis_object, object, state_type, state_vector_source, ""),
    n,
    `Spearman rho` = apa_r(estimate),
    `|rho|` = apa_num(abs_estimate, 2),
    p = apa_p(p.value),
    q = apa_p(p_fdr)
  )

apa_table(cosine_table, caption = "Table 4. Systematic comparison between the best quantum-like metric and the best classical cosine baseline.")
Table 4. Systematic comparison between the best quantum-like metric and the best classical cosine baseline.
Analysis block Comparison family Metric Metric source Representation n Spearman rho |rho| p q
H1 ipsative asymmetry Best classical cosine Centroid cosine classical_baselines contour_centroid 30 −.01 0.01 .971 .975
H1 ipsative asymmetry Best quantum-like metric Containment asymmetry pairwise_subspace_metrics S_lowdin 30 .90 0.90 < .001 < .001
H2 salience Best classical cosine Centroid cosine classical_baselines contour_centroid 30 .10 0.10 .586 .658
H2 salience Best quantum-like metric Containment asymmetry pairwise_subspace_metrics S_lowdin 30 −.90 0.90 < .001 < .001
H3 Likert similarity Best classical cosine Centroid cosine classical_baselines contour_centroid 89 .51 0.51 < .001 < .001
H3 Likert similarity Best quantum-like metric a in b trace normalized pairwise_operator_density_metrics rho_freq_density 89 .45 0.45 < .001 < .001
H6 choice full Best classical cosine Centroid cosine classical_baselines contour_centroid 66 .28 0.28 .024 .420
H6 choice full Best quantum-like metric frobenius distance pairwise_operator_density_metrics rho_fit_global_density 66 .40 0.40 < .001 .071
H6 rating Best classical cosine Centroid cosine classical_baselines contour_centroid 44 .24 0.24 .119 .723
H6 rating Best quantum-like metric Containment asymmetry pairwise_operator_density_metrics rho_freq_density 44 −.69 0.69 < .001 < .001
Show code
write_table(cosine_table, "table_04_cosine_vs_quantum_apa.csv")
Show code
cosine_plot <- cosine_comparisons %>%
  mutate(
    analysis_block = fct_inorder(analysis_block),
    comparison_family = fct_relevel(comparison_family, "Best classical cosine", "Best quantum-like metric")
  ) %>%
  ggplot(aes(x = analysis_block, y = estimate, fill = comparison_family)) +
  geom_hline(yintercept = 0, linewidth = .35) +
  geom_col(position = position_dodge(width = .72), width = .62) +
  coord_flip() +
  scale_y_continuous(limits = c(-1, 1), breaks = seq(-1, 1, .25)) +
  scale_fill_manual(values = report_palette) +
  labs(
    title = "Quantum-like metrics versus classical cosine",
    subtitle = "Best Spearman correlation within each family and analysis block.",
    x = NULL,
    y = "Spearman rho"
  ) +
  theme_apa7()

cosine_plot

Show code
save_plot(cosine_plot, "executive_cosine_vs_quantum_comparison.png", width = 8.5, height = 5.2)

9 Metric families and interpretive map

This map translates each computational family into a psychological interpretation. The notebook uses it repeatedly because the same statistical index, Spearman correlation, is applied to very different formal objects. Without this map, the analysis risks becoming a contest between filenames. A correlation produced by centroid cosine means something different from a correlation produced by projector containment, even if both are numerically similar.

Show code
metric_map <- tibble(
  `Metric family` = c(
    "Classical vector baseline",
    "Subspace/projector geometry",
    "Operator/density geometry",
    "Sequential QSM",
    "Contextualized diagnostic",
    "Perplexity-inhibited diagnostic"
  ),
  `Main object` = c(
    "Contour centroid vector",
    "Bases and projectors S, P",
    "Projectors and density-like matrices",
    "State plus ordered projections",
    "Contextual basis or Lüders-filtered density",
    "Residual state after removing confusing shared structure"
  ),
  `Psychological interpretation` = c(
    "Average semantic proximity; strong baseline for explicit similarity ratings.",
    "Conceptual inclusion, overlap, distance, incompatibility, and subspace geometry.",
    "State-like similarity and distinguishability between global/local conceptual structures.",
    "Order-sensitive similarity through sequential projection.",
    "How similarity changes when concepts are compared inside a context.",
    "How choices shift after inhibiting properties shared by a plausible competing pair."
  ),
  `Expected strongest domain` = c(
    "Likert similarity",
    "Asymmetry, salience, triplet structure",
    "Similarity and diagnostic ratings",
    "Asymmetry and order effects",
    "Diagnosticity with explicit context",
    "Diagnostic grouping/perplexity tasks"
  )
)

apa_table(metric_map, caption = "Table 5. Conceptual interpretation of computational metric families.")
Table 5. Conceptual interpretation of computational metric families.
Metric family Main object Psychological interpretation Expected strongest domain
Classical vector baseline Contour centroid vector Average semantic proximity; strong baseline for explicit similarity ratings. Likert similarity
Subspace/projector geometry Bases and projectors S, P Conceptual inclusion, overlap, distance, incompatibility, and subspace geometry. Asymmetry, salience, triplet structure
Operator/density geometry Projectors and density-like matrices State-like similarity and distinguishability between global/local conceptual structures. Similarity and diagnostic ratings
Sequential QSM State plus ordered projections Order-sensitive similarity through sequential projection. Asymmetry and order effects
Contextualized diagnostic Contextual basis or Lüders-filtered density How similarity changes when concepts are compared inside a context. Diagnosticity with explicit context
Perplexity-inhibited diagnostic Residual state after removing confusing shared structure How choices shift after inhibiting properties shared by a plausible competing pair. Diagnostic grouping/perplexity tasks
Show code
write_table(metric_map, "table_05_metric_family_interpretive_map.csv")

10 H1: ipsative asymmetry

10.1 Theoretical rationale

Ipsative asymmetry is the clearest test of whether similarity is more than symmetric proximity. In a classical cosine model, (sim(A,B)=sim(B,A)), so asymmetry has to be added externally. In the subspace model, asymmetry can emerge naturally from directional containment: concept (A) can be largely contained in concept (B) even when (B) is not equally contained in (A).

Psychologically, this corresponds to the idea that one concept may be treated as more specific, narrower, or more dependent on features that are also present in the other concept. The prediction is therefore not that two concepts are simply close, but that one conceptual region is included in the other to a greater degree. This is why H1 is interpreted primarily through containment asymmetry and only secondarily through sequential QSM.

10.2 Methodological choices for H1

Directional metrics are reoriented to match the human item order. This is essential: the computational files store pairs canonically, while the human task has a first and second item. Without reorientation, a good asymmetry metric would look unstable merely because the row order changed. Classical cosine baselines are included as a negative control: they can predict overall similarity, but they should not be able to predict direction unless direction is indirectly encoded by some confound.

H1 tests whether directional computational structure predicts forced-choice asymmetry. The primary theoretical metric is containment asymmetry: whether the first concept is more contained in the second than the second is contained in the first.

Show code
h1_top_directional <- h1_rank_directional %>% slice_head(n = params$top_n_metrics)
h1_family_best <- best_by_family(h1_rank_all)
h1_cosine_comp <- compare_quantum_vs_cosine(h1_rank_all)

apa_table(
  h1_top_directional %>%
    transmute(
      Metric = metric_short,
      Family = metric_family,
      Variant = str_trunc(variant_label, 50),
      Object = coalesce(basis_object, object, state_type, ""),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 6. H1 top directional quantum-like predictors of ipsative asymmetry."
)
Table 6. H1 top directional quantum-like predictors of ipsative asymmetry.
Metric Family Variant Object n Spearman rho p q
Containment asymmetry Subspace/projector geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho S_lowdin 30 .90 < .001 < .001
qsm neutral asymmetry a minus b Subspace/projector geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho S_lowdin 30 .90 < .001 < .001
Containment asymmetry Operator/density geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho P_concat_direct 30 .90 < .001 < .001
Containment asymmetry Operator/density geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho P_lowdin 30 .90 < .001 < .001
Containment asymmetry Operator/density geometry gmm_bic | g=gavish_donoho | l=gavish_donoho P_concat_direct 30 .86 < .001 < .001
Containment asymmetry Operator/density geometry gmm_bic | g=gavish_donoho | l=gavish_donoho P_lowdin 30 .86 < .001 < .001
Containment asymmetry Subspace/projector geometry gmm_bic | g=gavish_donoho | l=gavish_donoho S_lowdin 30 .86 < .001 < .001
qsm neutral asymmetry a minus b Subspace/projector geometry gmm_bic | g=gavish_donoho | l=gavish_donoho S_lowdin 30 .86 < .001 < .001
Containment asymmetry Operator/density geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho projector_mixture_freq 30 .83 < .001 < .001
Containment asymmetry Operator/density geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho projector_mixture_fit_global 30 .83 < .001 < .001
Containment asymmetry Operator/density geometry gmm_bic | g=permutation_pa | l=permutation_pa P_concat_direct 30 .79 < .001 < .001
Containment asymmetry Operator/density geometry gmm_bic | g=permutation_pa | l=permutation_pa P_lowdin 30 .79 < .001 < .001
Show code
apa_table(
  h1_cosine_comp %>%
    transmute(
      `Comparison family` = comparison_family,
      Metric = metric_short,
      Family = metric_family,
      Representation = coalesce(basis_object, object, state_type, ""),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 7. H1 comparison against the classical cosine baseline."
)
Table 7. H1 comparison against the classical cosine baseline.
Comparison family Metric Family Representation n Spearman rho p q
Best classical cosine Centroid cosine Classical vector baseline contour_centroid 30 −.01 .971 .975
Best quantum-like metric Containment asymmetry Subspace/projector geometry S_lowdin 30 .90 < .001 < .001
Show code
plot_ranked_correlations(
  h1_top_directional,
  title = "H1: directional predictors of ipsative asymmetry",
  subtitle = "Containment asymmetry should dominate if human asymmetry reflects conceptual inclusion.",
  n = params$top_n_plots,
  filename = "h1_top_directional_metric_correlations_v7.png"
)

Show code
h1_primary <- h1_top_directional %>% slice_head(n = 1)
plot_metric_scatter(
  h1_all,
  h1_primary,
  title = "H1 primary metric: containment asymmetry",
  y_label = "Human forced-choice proportion for first concept",
  filename = "h1_primary_metric_scatter_v7.png"
)

Show code
h1_best <- h1_top_directional %>% slice_head(n = 1)
h1_cos <- h1_cosine_comp %>% filter(comparison_family == "Best classical cosine") %>% slice_head(n = 1)
cat(
  "**Interpretation.** The best H1 predictor is ", h1_best$metric_short[[1]],
  " from the ", h1_best$metric_family[[1]], " family, with rho = ", apa_r(h1_best$estimate[[1]]),
  " (p ", apa_p(h1_best$p.value[[1]]), "). ",
  "This is ", interpret_corr_strength(h1_best$estimate[[1]]),
  ". The best classical cosine baseline reaches rho = ", apa_r(h1_cos$estimate[[1]]),
  ". Thus, H1 is mainly a directional subspace result rather than a simple vector-similarity result.\n"
)

Interpretation. The best H1 predictor is Containment asymmetry from the Subspace/projector geometry family, with rho = .90 (p < .001 ). This is very strong . The best classical cosine baseline reaches rho = −.01 . Thus, H1 is mainly a directional subspace result rather than a simple vector-similarity result.

11 H2: salience

11.1 Theoretical rationale

Salience is expected to modulate asymmetry. In Tversky-like accounts, a more salient or prototypical concept can dominate the comparison, making the less salient concept appear more similar to the salient one than the reverse. In the present subspace model, this can be reinterpreted geometrically: the more salient concept may behave as a broader or more diagnostic region of semantic structure, so the less salient concept is more contained in it than vice versa.

This means H2 should not merely correlate with similarity. It should correlate with the reverse of containment asymmetry if salience is functioning as the broad/prototypical pole of the relation. A strong negative association between H2 and the H1 containment metric is therefore theoretically coherent rather than inconvenient.

11.2 Methodological choices for H2

The same directional metrics used for H1 are tested here. This keeps the interpretation clean: H1 and H2 are not two unrelated analyses but two views of the same directional geometry. The relevant question is whether the salience data invert the asymmetry data, and whether this inversion is better captured by subspace containment than by centroid cosine.

H2 tests salience. The expected relation is theoretically close to H1, but usually with the opposite sign: if one concept functions as a broader or more salient reference, the containment asymmetry can reverse relative to ipsative choice.

Show code
h2_top_directional <- h2_rank_directional %>% slice_head(n = params$top_n_metrics)
h2_family_best <- best_by_family(h2_rank_all)
h2_cosine_comp <- compare_quantum_vs_cosine(h2_rank_all)

apa_table(
  h2_top_directional %>%
    transmute(
      Metric = metric_short,
      Family = metric_family,
      Variant = str_trunc(variant_label, 50),
      Object = coalesce(basis_object, object, state_type, ""),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 8. H2 top directional predictors of salience."
)
Table 8. H2 top directional predictors of salience.
Metric Family Variant Object n Spearman rho p q
Containment asymmetry Subspace/projector geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho S_lowdin 30 −.90 < .001 < .001
qsm neutral asymmetry a minus b Subspace/projector geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho S_lowdin 30 −.90 < .001 < .001
Containment asymmetry Operator/density geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho P_concat_direct 30 −.89 < .001 < .001
Containment asymmetry Operator/density geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho P_lowdin 30 −.89 < .001 < .001
Containment asymmetry Operator/density geometry gmm_bic | g=gavish_donoho | l=gavish_donoho P_concat_direct 30 −.88 < .001 < .001
Containment asymmetry Operator/density geometry gmm_bic | g=gavish_donoho | l=gavish_donoho P_lowdin 30 −.88 < .001 < .001
Containment asymmetry Subspace/projector geometry gmm_bic | g=gavish_donoho | l=gavish_donoho S_lowdin 30 −.88 < .001 < .001
qsm neutral asymmetry a minus b Subspace/projector geometry gmm_bic | g=gavish_donoho | l=gavish_donoho S_lowdin 30 −.88 < .001 < .001
Containment asymmetry Operator/density geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho projector_mixture_freq 30 −.80 < .001 < .001
Containment asymmetry Operator/density geometry bayesian_gmm | g=gavish_donoho | l=gavish_donoho projector_mixture_fit_global 30 −.79 < .001 < .001
Containment asymmetry Operator/density geometry gmm_bic | g=permutation_pa | l=permutation_pa P_concat_direct 30 −.79 < .001 < .001
Containment asymmetry Operator/density geometry gmm_bic | g=permutation_pa | l=permutation_pa P_lowdin 30 −.79 < .001 < .001
Show code
h1_h2_human <- pair_behavior %>%
  filter(hypothesis %in% c("H1_asymmetry_ipsative", "H2_salience")) %>%
  select(hypothesis, pair_id, human_value) %>%
  pivot_wider(names_from = hypothesis, values_from = human_value) %>%
  filter(!is.na(H1_asymmetry_ipsative), !is.na(H2_salience))

h1_h2_link <- safe_cor(h1_h2_human, x = "H1_asymmetry_ipsative", y = "H2_salience")

apa_table(
  h1_h2_link %>%
    transmute(
      Comparison = "H1 human asymmetry vs H2 human salience",
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value)
    ),
  caption = "Table 9. Association between human ipsative asymmetry and human salience."
)
Table 9. Association between human ipsative asymmetry and human salience.
Comparison n Spearman rho p
H1 human asymmetry vs H2 human salience 30 −.95 < .001
Show code
apa_table(
  h2_cosine_comp %>%
    transmute(
      `Comparison family` = comparison_family,
      Metric = metric_short,
      Family = metric_family,
      Representation = coalesce(basis_object, object, state_type, ""),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 10. H2 comparison against the classical cosine baseline."
)
Table 10. H2 comparison against the classical cosine baseline.
Comparison family Metric Family Representation n Spearman rho p q
Best classical cosine Centroid cosine Classical vector baseline contour_centroid 30 .10 .586 .658
Best quantum-like metric Containment asymmetry Subspace/projector geometry S_lowdin 30 −.90 < .001 < .001
Show code
plot_ranked_correlations(
  h2_top_directional,
  title = "H2: directional predictors of salience",
  subtitle = "Negative associations indicate that salience reverses the H1 containment direction.",
  n = params$top_n_plots,
  filename = "h2_top_directional_metric_correlations_v7.png"
)

Show code
h2_primary <- h2_top_directional %>% slice_head(n = 1)
plot_metric_scatter(
  h2_all,
  h2_primary,
  title = "H2 primary metric: salience and containment",
  y_label = "Human salience score",
  filename = "h2_primary_metric_scatter_v7.png"
)

Show code
h2_best <- h2_top_directional %>% slice_head(n = 1)
h2_cos <- h2_cosine_comp %>% filter(comparison_family == "Best classical cosine") %>% slice_head(n = 1)
cat(
  "**Interpretation.** The best H2 predictor is ", h2_best$metric_short[[1]],
  " with rho = ", apa_r(h2_best$estimate[[1]]), " (p ", apa_p(h2_best$p.value[[1]]), "). ",
  "The sign is informative: salience behaves as the reverse side of the directional containment relation. ",
  "The best classical cosine baseline reaches rho = ", apa_r(h2_cos$estimate[[1]]),
  ", so the salience result is again not reducible to simple centroid similarity.\n"
)

Interpretation. The best H2 predictor is Containment asymmetry with rho = −.90 (p < .001 ). The sign is informative: salience behaves as the reverse side of the directional containment relation. The best classical cosine baseline reaches rho = .10 , so the salience result is again not reducible to simple centroid similarity.

11.3 H2b: single-concept Likert salience and representational dimensionality

The previous H2 analysis concerns ipsative salience: participants choose which member of a pair feels more salient. The dataset also contains a second salience measure: single-concept Likert salience ratings. This is a different psychological object. It is not directional and it is not pairwise. It asks whether a concept, considered on its own, appears cognitively salient or prominent.

Because this task is concept-level, pairwise cosine is not the right baseline. A more natural predictor is representational richness: how many dimensions are needed to describe the saturated contour of a concept, how many occurrences it has in the corpus, and how many local semantic regions can be estimated. In the theory, a concept with a richer semantic contour should occupy a broader, more differentiated subspace in the container. If participants’ Likert salience tracks this richness, then dimensionality should predict ratings. If frequency alone explains the effect, then the result is less specifically quantum-like and more about linguistic exposure. Annoyingly reasonable, but important.

The arbitrary decisions here are explicit. First, the analysis treats saliency_2.csv as a single-concept Likert task and averages ratings by target. Second, the predictor set includes several dimensionality criteria rather than pretending that one rule is metaphysically ordained. Third, log corpus frequency is included as a baseline and as a covariate, because dimensionality and occurrence frequency are naturally entangled in corpus data. The statistical question is therefore twofold: (a) does dimensionality correlate with Likert salience? and (b) does it add anything beyond raw linguistic frequency?

Show code
h2_likert_top <- h2_likert_rank %>% slice_head(n = params$top_n_metrics)

apa_table(
  h2_likert_top %>%
    transmute(
      Predictor = metric_short,
      Family = metric_family,
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 11. H2b concept-level predictors of single-concept Likert salience."
)
Table 11. H2b concept-level predictors of single-concept Likert salience.
Predictor Family n Spearman rho p q
Log frequency Corpus frequency baseline 44 .89 < .001 < .001
Raw frequency Corpus frequency baseline 44 .89 < .001 < .001
Total local Gavish, GMM-BIC Local representational richness 44 .89 < .001 < .001
Marchenko-Pastur dimensionality Global representational dimensionality 44 .89 < .001 < .001
Total local Gavish, Bayesian GMM Local representational richness 44 .89 < .001 < .001
Total local Horn, GMM-BIC Local representational richness 44 .88 < .001 < .001
Mean local Gavish, Bayesian GMM Local representational richness 44 .88 < .001 < .001
Gavish-Donoho dimensionality Global representational dimensionality 44 .87 < .001 < .001
Estimable local clusters, GMM-BIC Local representational richness 44 .87 < .001 < .001
Dimensions for 95% variance Global representational dimensionality 44 .87 < .001 < .001
Dimensions for 90% variance Global representational dimensionality 44 .87 < .001 < .001
Dimensions for 80% variance Global representational dimensionality 44 .86 < .001 < .001
Show code
apa_table(
  h2_likert_frequency_adjusted %>%
    slice_head(n = params$top_n_metrics) %>%
    transmute(
      Predictor = metric_short,
      n,
      `beta predictor` = apa_r(beta_predictor),
      `p predictor` = apa_p(p_predictor),
      `q predictor` = apa_p(p_fdr_predictor),
      `beta log frequency` = apa_r(beta_log_frequency),
      `p log frequency` = apa_p(p_log_frequency),
      `R2` = apa_num(r_squared, 2)
    ),
  caption = "Table 12. H2b dimensionality predictors of Likert salience adjusted for log corpus frequency."
)
Table 12. H2b dimensionality predictors of Likert salience adjusted for log corpus frequency.
Predictor n beta predictor p predictor q predictor beta log frequency p log frequency R2
Mean local Gavish, GMM-BIC 44 −.21 .068 .623 1.03 < .001 0.78
Estimable local clusters, GMM-BIC 44 .32 .089 .623 .58 .003 0.78
Marchenko-Pastur dimensionality 44 .87 .105 .623 .01 .979 0.78
Individual variance ≥ .005 44 −.15 .149 .623 .77 < .001 0.78
Mean local Gavish, Bayesian GMM 44 .27 .159 .623 .63 .002 0.77
Gavish-Donoho dimensionality 44 .24 .170 .623 .66 < .001 0.77
Mean local Horn, GMM-BIC 44 −.11 .242 .686 .93 < .001 0.77
Profile-likelihood dimensionality 44 −.10 .249 .686 .82 < .001 0.77
Total local Horn, GMM-BIC 44 .25 .304 .743 .63 .012 0.77
Mean local Horn, Bayesian GMM 44 .09 .412 .907 .81 < .001 0.77
Broken-stick dimensionality 44 .12 .475 .949 .77 < .001 0.77
Effective rank 44 −.05 .622 .951 .91 < .001 0.76
Show code
write_table(salience_likert_summary, "h2b_likert_salience_by_target.csv")
write_table(h2_likert_rank, "h2b_likert_salience_dimensionality_correlations.csv")
write_table(h2_likert_frequency_adjusted, "h2b_likert_salience_frequency_adjusted_models.csv")
Show code
h2b_rank_plot <- h2_likert_rank %>%
  filter(!is.na(estimate)) %>%
  slice_head(n = params$top_n_plots) %>%
  mutate(
    label = fct_reorder(str_wrap(metric_short, 32), abs_estimate),
    significant = if_else(!is.na(p_fdr) & p_fdr < .05, "FDR < .05", "FDR ≥ .05")
  ) %>%
  ggplot(aes(x = label, y = estimate, fill = metric_family)) +
  geom_hline(yintercept = 0, linewidth = .35) +
  geom_col(width = .72) +
  coord_flip() +
  scale_y_continuous(limits = c(-1, 1), breaks = seq(-1, 1, .25)) +
  scale_fill_manual(values = c(
    "Corpus frequency baseline" = "#D55E00",
    "Global representational dimensionality" = "#0072B2",
    "Local representational richness" = "#009E73"
  )) +
  labs(
    title = "H2b: single-concept Likert salience",
    subtitle = "Concept-level salience is compared with frequency and representational dimensionality.",
    x = NULL,
    y = "Spearman rho"
  ) +
  theme_apa7()

h2b_rank_plot

Show code
save_plot(h2b_rank_plot, "h2b_likert_salience_dimensionality_correlations_v8.png", width = 9, height = 6)

h2b_best_predictor <- h2_likert_rank %>% slice_head(n = 1)
h2b_best_data <- h2_likert_predictor_long %>% filter(metric_id == h2b_best_predictor$metric_id[[1]])

h2b_best_scatter <- h2b_best_data %>%
  ggplot(aes(x = metric_value, y = human_value)) +
  geom_point(size = 2.4, alpha = .80) +
  geom_smooth(method = "lm", se = TRUE, linewidth = .75) +
  labs(
    title = "Best concept-level predictor of Likert salience",
    subtitle = paste0(h2b_best_predictor$metric_short[[1]], "; rho = ", apa_r(h2b_best_predictor$estimate[[1]]), ", p ", apa_p(h2b_best_predictor$p.value[[1]])),
    x = h2b_best_predictor$metric_short[[1]],
    y = "Mean Likert salience"
  ) +
  theme_apa7()

h2b_best_scatter

Show code
save_plot(h2b_best_scatter, "h2b_likert_salience_best_predictor_scatter_v8.png", width = 7.5, height = 5)

h2b_freq_scatter <- salience_likert_dimensionality %>%
  ggplot(aes(x = log10_n_occurrences, y = mean_salience_likert)) +
  geom_point(size = 2.4, alpha = .80) +
  geom_smooth(method = "lm", se = TRUE, linewidth = .75) +
  labs(
    title = "Frequency baseline for Likert salience",
    subtitle = "This checks whether rated salience mostly reflects how often a concept occurs in the corpus.",
    x = "Log10 corpus occurrences",
    y = "Mean Likert salience"
  ) +
  theme_apa7()

h2b_freq_scatter

Show code
save_plot(h2b_freq_scatter, "h2b_likert_salience_frequency_scatter_v8.png", width = 7.5, height = 5)
Show code
h2b_best <- h2_likert_rank %>% slice_head(n = 1)
h2b_freq <- h2_likert_rank %>% filter(metric_name == "log10_n_occurrences") %>% slice_head(n = 1)
h2b_adjusted_best <- h2_likert_frequency_adjusted %>% slice_head(n = 1)
cat(
  "**Interpretation.** The strongest concept-level predictor of Likert salience is ", h2b_best$metric_short[[1]],
  " with rho = ", apa_r(h2b_best$estimate[[1]]), " (p ", apa_p(h2b_best$p.value[[1]]), "). ",
  "The log-frequency baseline reaches rho = ", apa_r(h2b_freq$estimate[[1]]), ". ",
  "The frequency-adjusted models test whether dimensionality has predictive value over and above exposure. ",
  "The strongest adjusted predictor is ", h2b_adjusted_best$metric_short[[1]],
  " with beta = ", apa_r(h2b_adjusted_best$beta_predictor[[1]]),
  " (p ", apa_p(h2b_adjusted_best$p_predictor[[1]]), "). ",
  "If the unadjusted dimensionality correlations are strong but the adjusted effects weaken, the interpretation should be cautious: Likert salience is tracking representational richness, but much of that richness may be coupled to corpus prevalence. That is still theoretically informative, because it separates ipsative salience as directional containment from Likert salience as global availability/richness.\n"
)

Interpretation. The strongest concept-level predictor of Likert salience is Log frequency with rho = .89 (p < .001 ). The log-frequency baseline reaches rho = .89 . The frequency-adjusted models test whether dimensionality has predictive value over and above exposure. The strongest adjusted predictor is Mean local Gavish, GMM-BIC with beta = −.21 (p .068 ). If the unadjusted dimensionality correlations are strong but the adjusted effects weaken, the interpretation should be cautious: Likert salience is tracking representational richness, but much of that richness may be coupled to corpus prevalence. That is still theoretically informative, because it separates ipsative salience as directional containment from Likert salience as global availability/richness.

11.4 H2c: planned salience/similarity condition structure from the thesis annex

The annex design adds an important layer that is easy to miss if we only look at hypothesis-level correlations. The asymmetry/salience country pairs were not arbitrary: they were prepared to cross planned salience and planned similarity. This means the same human and computational results can be read not only pair by pair, but also by experimental condition.

This gives four useful analyses:

  1. Annex audit. Check whether each planned pair is present in the current human-aligned dataset. This is especially important because one pair differs between the thesis listing and the empirical response file: the thesis list includes Mexico–Colombia, while the current salience/asymmetry files include Ecuador–Peru. The notebook keeps both entries and flags their source.
  2. Concept-level salience manipulation check. Concepts labelled high-salience in the annex should receive higher single-concept Likert salience ratings than low-salience concepts. If dimensionality also differs by planned salience, then dimensionality may be part of the computational signature of salience.
  3. Pair-level salience choice check. In mixed high–low pairs, ipsative salience should select the high-salience concept. In low–low and high–high pairs, the task should be less strongly determined by the planned salience manipulation.
  4. Similarity manipulation check. Pairs labelled high-similarity should show higher human Likert similarity and higher computational similarity than low-similarity pairs.

The theoretical point is that salience is probably not a single thing. Ipsative salience is a directional comparison between two concepts. Likert salience is a concept-level availability/richness judgment. Planned salience cells let us test how these two levels relate without pretending they are the same psychological operation, a small mercy for everyone involved.

Show code
apa_table(
  annex_condition_audit %>%
    transmute(
      `Concept 1` = planned_concept_1_es,
      `Concept 2` = planned_concept_2_es,
      `Planned salience` = paste(planned_salience_1, planned_salience_2, sep = "--"),
      `Planned similarity` = as.character(planned_similarity),
      `Annex condition` = as.character(annex_condition),
      `Observed in human pair file` = observed_in_human_pair_design,
      Source = condition_source
    ),
  caption = "Table H2c-1. Audit of planned salience/similarity conditions from the thesis annex."
)
Table H2c-1. Audit of planned salience/similarity conditions from the thesis annex.
Concept 1 Concept 2 Planned salience Planned similarity Annex condition Observed in human pair file Source
Ghana Luxemburgo low--low low low_low__low_similarity TRUE thesis_annex
Islas Feroe Singapur low--low low low_low__low_similarity TRUE thesis_annex
Burkina Faso Guayana Francesa low--low low low_low__low_similarity TRUE thesis_annex
Mongolia Bulgaria low--low low low_low__low_similarity TRUE thesis_annex
Omán Belice low--low low low_low__low_similarity TRUE thesis_annex
Eslovenia Eslovaquia low--low high low_low__high_similarity TRUE thesis_annex
Zambia Zimbabue low--low high low_low__high_similarity TRUE thesis_annex
Laos Camboya low--low high low_low__high_similarity TRUE thesis_annex
Omán Yemen low--low high low_low__high_similarity TRUE thesis_annex
Turkmenistán Uzbekistán low--low high low_low__high_similarity TRUE thesis_annex
Francia Bolivia high--low low high_low__low_similarity TRUE thesis_annex
Estados Unidos Belice high--low low high_low__low_similarity TRUE thesis_annex
China Luxemburgo high--low low high_low__low_similarity TRUE thesis_annex
México Laos high--low low high_low__low_similarity TRUE thesis_annex
Rusia Burkina Faso high--low low high_low__low_similarity TRUE thesis_annex
Francia Bélgica high--low high high_low__high_similarity TRUE thesis_annex
España Andorra high--low high high_low__high_similarity TRUE thesis_annex
China Singapur high--low high high_low__high_similarity TRUE thesis_annex
Rusia Bielorrusia high--low high high_low__high_similarity TRUE thesis_annex
Argentina Paraguay high--low high high_low__high_similarity TRUE thesis_annex
Francia Brasil high--high low high_high__low_similarity TRUE thesis_annex
Alemania Canadá high--high low high_high__low_similarity TRUE thesis_annex
Italia Japón high--high low high_high__low_similarity TRUE thesis_annex
Marruecos Argentina high--high low high_high__low_similarity TRUE thesis_annex
India México high--high low high_high__low_similarity TRUE thesis_annex
Italia España high--high high high_high__high_similarity TRUE thesis_annex
Reino Unido Estados Unidos high--high high high_high__high_similarity TRUE thesis_annex
Suecia Noruega high--high high high_high__high_similarity TRUE thesis_annex
Brasil Portugal high--high high high_high__high_similarity TRUE thesis_annex
México Colombia high--high high high_high__high_similarity FALSE thesis_annex_not_observed_in_current_human_pair_file
Ecuador Perú high--high high high_high__high_similarity TRUE observed_human_file_replacement_or_extra
Show code
apa_table(
  h2c_human_condition_summary %>%
    transmute(
      Hypothesis = hypothesis,
      `Annex condition` = as.character(annex_condition),
      `Presentation salience` = as.character(presentation_salience_pattern),
      `Planned similarity` = as.character(planned_similarity),
      `n items` = n_items,
      `Mean human value` = apa_num(mean_human_value, 3),
      `Mean |deviation from .5|` = apa_num(mean_abs_deviation_from_chance, 3),
      `Mean P(choose high salience)` = apa_num(mean_prop_choose_high_salience, 3)
    ),
  caption = "Table H2c-2. Human responses summarized by planned annex condition."
)
Table H2c-2. Human responses summarized by planned annex condition.
Hypothesis Annex condition Presentation salience Planned similarity n items Mean human value Mean |deviation from .5| Mean P(choose high salience)
H1_asymmetry_ipsative low_low__low_similarity low_low low 5 0.698 0.198
H1_asymmetry_ipsative low_low__high_similarity low_low high 5 0.575 0.075
H1_asymmetry_ipsative high_low__low_similarity low_high low 2 0.904 0.404
H1_asymmetry_ipsative high_low__low_similarity high_low low 3 0.118 0.382
H1_asymmetry_ipsative high_low__high_similarity low_high high 2 0.957 0.457
H1_asymmetry_ipsative high_low__high_similarity high_low high 3 0.162 0.338
H1_asymmetry_ipsative high_high__low_similarity high_high low 5 0.378 0.122
H1_asymmetry_ipsative high_high__high_similarity high_high high 5 0.620 0.120
H2_salience low_low__low_similarity low_low low 5 0.266 0.234
H2_salience low_low__high_similarity low_low high 5 0.442 0.103
H2_salience high_low__low_similarity low_high low 2 0.030 0.470 0.970
H2_salience high_low__low_similarity high_low low 3 0.963 0.463 0.963
H2_salience high_low__high_similarity low_high high 2 0.020 0.480 0.980
H2_salience high_low__high_similarity high_low high 3 0.905 0.405 0.905
H2_salience high_high__low_similarity high_high low 5 0.683 0.189
H2_salience high_high__high_similarity high_high high 5 0.392 0.154
H3_likert_order_asymmetry_H4_triangle_network low_low__low_similarity low_low low 5 3.672 3.172
H3_likert_order_asymmetry_H4_triangle_network low_low__high_similarity low_low high 5 6.114 5.614
H3_likert_order_asymmetry_H4_triangle_network high_low__low_similarity low_high low 5 3.043 2.543
H3_likert_order_asymmetry_H4_triangle_network high_low__low_similarity high_low low 5 2.986 2.486
H3_likert_order_asymmetry_H4_triangle_network high_low__high_similarity low_high high 5 6.950 6.450
H3_likert_order_asymmetry_H4_triangle_network high_low__high_similarity high_low high 5 6.826 6.326
H3_likert_order_asymmetry_H4_triangle_network high_high__low_similarity high_high low 5 3.788 3.288
H3_likert_order_asymmetry_H4_triangle_network high_high__high_similarity high_high high 5 7.027 6.527
Show code
write_table(annex_condition_audit, "h2c_annex_condition_audit.csv")
write_table(h2c_human_condition_summary, "h2c_human_condition_summary.csv")
Show code
apa_table(
  h2c_concept_condition_summary %>%
    transmute(
      `Planned concept salience` = planned_salience_label,
      `n concepts` = n_concepts,
      `Mean Likert salience` = apa_num(mean_likert_salience, 2),
      `Mean log frequency` = apa_num(mean_log10_frequency, 2),
      `Mean effective rank` = apa_num(mean_effective_rank, 2),
      `Mean Horn dimensionality` = apa_num(mean_horn_dimensionality, 2),
      `Mean Gavish dimensionality` = apa_num(mean_gavish_dimensionality, 2),
      `Mean permutation PA dimensionality` = apa_num(mean_permutation_dimensionality, 2)
    ),
  caption = "Table H2c-3. Concept-level salience, frequency, and dimensionality by planned salience label."
)
Table H2c-3. Concept-level salience, frequency, and dimensionality by planned salience label.
Planned concept salience n concepts Mean Likert salience Mean log frequency Mean effective rank Mean Horn dimensionality Mean Gavish dimensionality Mean permutation PA dimensionality
high 20 6.22 3.90 300.00 65.00 41.50 62.20
low 24 3.18 2.58 229.88 43.75 21.92 35.12
Show code
apa_table(
  h2c_concept_condition_tests %>%
    transmute(
      Outcome = outcome_label,
      `n high` = n_high,
      `n low` = n_low,
      `Mean high` = apa_num(mean_high, 2),
      `Mean low` = apa_num(mean_low, 2),
      `High − low` = apa_num(difference_high_minus_low, 2),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table H2c-4. Planned high-vs-low salience differences in ratings and representational richness."
)
Table H2c-4. Planned high-vs-low salience differences in ratings and representational richness.
Outcome n high n low Mean high Mean low High − low p q
Log frequency 20 24 3.90 2.58 1.31 < .001 < .001
Mean Likert salience 20 24 6.22 3.18 3.03 < .001 < .001
Gavish-Donoho dimensionality 20 24 41.50 21.92 19.58 < .001 < .001
Permutation PA dimensionality 20 24 62.20 35.12 27.08 < .001 < .001
Horn/Wishart dimensionality 20 24 65.00 43.75 21.25 < .001 < .001
Profile-likelihood dimensionality 20 24 84.25 118.04 -33.79 < .001 < .001
Effective rank 20 24 300.00 229.88 70.12 .001 .001
Show code
write_table(h2c_concept_condition_summary, "h2c_concept_condition_summary.csv")
write_table(h2c_concept_condition_tests, "h2c_concept_condition_tests.csv")
Show code
apa_table(
  h2c_similarity_condition_summary %>%
    transmute(
      `Planned similarity` = as.character(planned_similarity),
      `Planned salience pair` = as.character(planned_salience_pair),
      `n pairs` = n_pairs,
      `Mean human similarity` = apa_num(mean_human_similarity, 2),
      `SD human similarity` = apa_num(sd_human_similarity, 2)
    ),
  caption = "Table H2c-5. Human Likert similarity by planned similarity and salience condition."
)
Table H2c-5. Human Likert similarity by planned similarity and salience condition.
Planned similarity Planned salience pair n pairs Mean human similarity SD human similarity
low low_low 5 3.67 0.68
low high_low 5 3.01 0.31
low high_high 5 3.79 1.09
high low_low 5 6.11 0.61
high high_low 5 6.89 0.50
high high_high 5 7.15 0.59
Show code
apa_table(
  h2c_similarity_condition_lm %>%
    transmute(
      Term = term,
      Estimate = apa_num(estimate, 3),
      SE = apa_num(std.error, 3),
      statistic = apa_num(statistic, 2),
      p = apa_p(p.value)
    ),
  caption = "Table H2c-6. Linear model predicting human Likert similarity from planned annex conditions."
)
Table H2c-6. Linear model predicting human Likert similarity from planned annex conditions.
Term Estimate SE statistic p
(Intercept) 3.281 0.263 12.45 < .001
planned_similarityhigh 3.224 0.263 12.24 < .001
planned_salience_pairhigh_low 0.058 0.323 0.18 .859
planned_salience_pairhigh_high 0.573 0.323 1.78 .087
Show code
apa_table(
  h2c_pairwise_condition_metric_tests %>%
    slice_head(n = params$top_n_metrics) %>%
    transmute(
      Metric = metric_short,
      Family = metric_family,
      Object = coalesce(basis_object, object, state_type, ""),
      `Mean high similarity` = apa_num(mean_high, 3),
      `Mean low similarity` = apa_num(mean_low, 3),
      `High − low` = apa_num(difference_high_minus_low, 3),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table H2c-7. Computational metrics that best separate planned high- vs low-similarity pairs."
)
Table H2c-7. Computational metrics that best separate planned high- vs low-similarity pairs.
Metric Family Object Mean high similarity Mean low similarity High − low p q
frobenius distance sq Operator/density geometry P_concat_direct -40.489 -95.634 55.145 .088 .785
frobenius distance sq Operator/density geometry P_lowdin -40.489 -95.634 55.145 .088 .785
frobenius distance sq Operator/density geometry P_lowdin -31.667 -83.611 51.944 .053 .785
frobenius distance sq Operator/density geometry P_concat_direct -31.667 -83.611 51.944 .053 .785
principal overlap nuclear Subspace/projector geometry S_lowdin 244.396 204.169 40.228 .278 .799
frobenius distance sq Operator/density geometry P_concat_direct -63.982 -104.087 40.105 .255 .799
frobenius distance sq Operator/density geometry P_lowdin -63.982 -104.087 40.105 .255 .799
trace inner Operator/density geometry P_concat_direct 242.300 202.728 39.572 .299 .799
trace inner Operator/density geometry P_lowdin 242.300 202.728 39.572 .299 .799
principal overlap trace Subspace/projector geometry S_lowdin 242.300 202.728 39.572 .299 .799
principal overlap nuclear Subspace/projector geometry S_lowdin 215.812 177.644 38.167 .399 .799
trace inner Operator/density geometry P_concat_direct 213.989 176.550 37.439 .415 .799
Show code
write_table(h2c_similarity_condition_summary, "h2c_similarity_condition_summary.csv")
write_table(h2c_similarity_condition_lm, "h2c_similarity_condition_lm.csv")
write_table(h2c_pairwise_condition_metric_tests, "h2c_pairwise_condition_metric_tests.csv")
Show code
h2c_condition_plot <- pair_behavior_with_conditions %>%
  filter(hypothesis %in% c("H1_asymmetry_ipsative", "H2_salience"), !is.na(presentation_salience_pattern)) %>%
  mutate(
    hypothesis_label = recode(
      hypothesis,
      H1_asymmetry_ipsative = "H1 ipsative asymmetry",
      H2_salience = "H2 ipsative salience"
    )
  ) %>%
  ggplot(aes(x = presentation_salience_pattern, y = human_value, fill = planned_similarity)) +
  geom_hline(yintercept = .5, linewidth = .35, linetype = "dashed") +
  geom_boxplot(width = .65, alpha = .75, outlier.shape = NA) +
  geom_jitter(aes(color = planned_similarity), width = .08, height = 0, size = 2, alpha = .80, show.legend = FALSE) +
  facet_wrap(~ hypothesis_label, ncol = 1) +
  scale_fill_manual(values = c("low" = "#E69F00", "high" = "#0072B2")) +
  scale_color_manual(values = c("low" = "#E69F00", "high" = "#0072B2")) +
  scale_y_continuous(limits = c(0, 1), breaks = seq(0, 1, .25)) +
  labs(
    title = "Ipsative responses by planned salience condition",
    subtitle = "The dashed line marks chance-level choice. Presentation order is separated from the annex condition.",
    x = "Presentation salience pattern",
    y = "Proportion choosing first option"
  ) +
  theme_apa7()

h2c_condition_plot

Show code
save_plot(h2c_condition_plot, "h2c_ipsative_responses_by_salience_condition_v9.png", width = 8.5, height = 7)

h2c_concept_plot <- h2c_likert_condition_dimensionality %>%
  pivot_longer(
    cols = any_of(c("mean_salience_likert", "log10_n_occurrences", "horn_wishart", "gavish_donoho", "permutation_pa")),
    names_to = "outcome",
    values_to = "value"
  ) %>%
  mutate(outcome = recode(outcome, !!!salience_predictor_labels, mean_salience_likert = "Mean Likert salience", log10_n_occurrences = "Log frequency", .default = outcome)) %>%
  ggplot(aes(x = planned_salience_label, y = value, fill = planned_salience_label)) +
  geom_boxplot(width = .55, alpha = .70, outlier.shape = NA) +
  geom_jitter(width = .08, size = 1.9, alpha = .75) +
  facet_wrap(~ outcome, scales = "free_y", ncol = 2) +
  scale_fill_manual(values = c("low" = "#999999", "high" = "#0072B2")) +
  labs(
    title = "Concept-level planned salience, ratings, and dimensionality",
    subtitle = "This evaluates whether high-salience concepts are also richer or more frequent in the corpus-derived representation.",
    x = "Planned concept salience",
    y = NULL
  ) +
  theme_apa7() +
  theme(legend.position = "none")

h2c_concept_plot

Show code
save_plot(h2c_concept_plot, "h2c_concept_salience_dimensionality_by_planned_label_v9.png", width = 9, height = 7)

h2c_best_similarity_metric <- h2c_pairwise_condition_metric_tests %>% slice_head(n = 1)
h2c_best_similarity_data <- pairwise_metric_values %>%
  inner_join(annex_salience_conditions %>% select(pair_id, planned_similarity, planned_salience_pair), by = "pair_id") %>%
  filter(metric_id == h2c_best_similarity_metric$metric_id[[1]]) %>%
  mutate(metric_value = similarity_orient_value(metric_name, value))

h2c_similarity_metric_plot <- h2c_best_similarity_data %>%
  ggplot(aes(x = planned_similarity, y = metric_value, fill = planned_similarity)) +
  geom_boxplot(width = .55, alpha = .75, outlier.shape = NA) +
  geom_jitter(width = .08, size = 2, alpha = .75) +
  scale_fill_manual(values = c("low" = "#E69F00", "high" = "#0072B2")) +
  labs(
    title = "Best computational separator of planned similarity",
    subtitle = paste0(h2c_best_similarity_metric$metric_short[[1]], "; high − low = ", apa_num(h2c_best_similarity_metric$difference_high_minus_low[[1]], 3)),
    x = "Planned similarity condition",
    y = "Computational similarity metric"
  ) +
  theme_apa7() +
  theme(legend.position = "none")

h2c_similarity_metric_plot

Show code
save_plot(h2c_similarity_metric_plot, "h2c_best_computational_similarity_condition_separator_v9.png", width = 7.5, height = 5)
Show code
h2c_salience_rating_diff <- h2c_concept_condition_tests %>% filter(outcome == "mean_salience_likert") %>% slice_head(n = 1)
h2c_frequency_diff <- h2c_concept_condition_tests %>% filter(outcome == "log10_n_occurrences") %>% slice_head(n = 1)
h2c_best_condition_metric <- h2c_pairwise_condition_metric_tests %>% slice_head(n = 1)
cat(
  "**Interpretation.** The annex condition structure lets us separate salience as an experimental manipulation from salience as an estimated psychological variable. ",
  "High-salience concepts differ from low-salience concepts in mean Likert salience by ", apa_num(h2c_salience_rating_diff$difference_high_minus_low[[1]], 2),
  " points (p ", apa_p(h2c_salience_rating_diff$p.value[[1]]), "). ",
  "The corresponding high-minus-low log-frequency difference is ", apa_num(h2c_frequency_diff$difference_high_minus_low[[1]], 2),
  " (p ", apa_p(h2c_frequency_diff$p.value[[1]]), "). ",
  "This matters because any dimensionality effect has to be read against frequency: rich concepts may be rich because they are semantically differentiated, but also because they appear more often in the corpus. ",
  "For planned similarity, the best computational separator is ", h2c_best_condition_metric$metric_short[[1]],
  " from the ", h2c_best_condition_metric$metric_family[[1]], " family, with high-minus-low difference = ",
  apa_num(h2c_best_condition_metric$difference_high_minus_low[[1]], 3), ". ",
  "Substantively, these condition analyses should be treated as manipulation checks and moderation analyses: they tell us whether the stimuli behaved as designed, and whether the quantum-like metrics are tracking the intended salience/similarity structure rather than merely fitting item-level noise. Which would be embarrassing, though hardly unprecedented in human affairs.\n"
)

Interpretation. The annex condition structure lets us separate salience as an experimental manipulation from salience as an estimated psychological variable. High-salience concepts differ from low-salience concepts in mean Likert salience by 3.03 points (p < .001 ). The corresponding high-minus-low log-frequency difference is 1.31 (p < .001 ). This matters because any dimensionality effect has to be read against frequency: rich concepts may be rich because they are semantically differentiated, but also because they appear more often in the corpus. For planned similarity, the best computational separator is frobenius distance sq from the Operator/density geometry family, with high-minus-low difference = 55.145 . Substantively, these condition analyses should be treated as manipulation checks and moderation analyses: they tell us whether the stimuli behaved as designed, and whether the quantum-like metrics are tracking the intended salience/similarity structure rather than merely fitting item-level noise. Which would be embarrassing, though hardly unprecedented in human affairs.

11.5 H2d: salience/similarity conditions as moderators of other contrasts

The planned salience cells should not only be used as a manipulation check for salience itself. They can also qualify the other contrasts built from the same country pairs. The theoretical reason is simple: if salience changes the diagnostic weight of a concept, then it should affect forced-choice asymmetry, Likert order asymmetry, and even how strongly computational containment predicts human responses. If this moderation is absent, the salience manipulation may still predict salience choices but not the more general comparison process.

The analyses below are deliberately exploratory because several cells contain only five planned pairs. They should be read as condition diagnostics, not as final hypothesis tests polished for ceremonial sacrifice to Reviewer 2.

Show code
safe_condition_lm <- function(data, outcome_col, rhs, analysis_label) {
  d <- data %>%
    filter(!is.na(.data[[outcome_col]]), is.finite(.data[[outcome_col]]))

  formula_text <- paste(outcome_col, "~", rhs)
  vars <- setdiff(all.vars(as.formula(formula_text)), outcome_col)
  usable <- nrow(d) >= 6 && all(vapply(vars, function(v) v %in% names(d) && n_distinct(d[[v]][!is.na(d[[v]])]) >= 2, logical(1)))

  if (!usable) {
    return(tibble(
      analysis_block = analysis_label,
      term = "model_not_estimable",
      estimate = NA_real_,
      std.error = NA_real_,
      statistic = NA_real_,
      p.value = NA_real_,
      n = nrow(d),
      formula = formula_text
    ))
  }

  tryCatch(
    broom::tidy(lm(as.formula(formula_text), data = d)) %>%
      mutate(analysis_block = analysis_label, n = nrow(d), formula = formula_text) %>%
      select(analysis_block, term, estimate, std.error, statistic, p.value, n, formula),
    error = function(e) tibble(
      analysis_block = analysis_label,
      term = paste0("model_error: ", e$message),
      estimate = NA_real_,
      std.error = NA_real_,
      statistic = NA_real_,
      p.value = NA_real_,
      n = nrow(d),
      formula = formula_text
    )
  )
}

h1_condition_data <- pair_behavior_with_conditions %>%
  filter(hypothesis == "H1_asymmetry_ipsative", !is.na(presentation_salience_pattern), !is.na(planned_similarity))

h2_condition_data <- pair_behavior_with_conditions %>%
  filter(hypothesis == "H2_salience", !is.na(presentation_salience_pattern), !is.na(planned_similarity))

h3_similarity_condition_data <- h3_similarity_human %>%
  inner_join(
    annex_salience_conditions %>% select(pair_id, planned_salience_pair, planned_similarity, annex_condition),
    by = "pair_id"
  ) %>%
  filter(!is.na(planned_salience_pair), !is.na(planned_similarity))

h3_order_condition_data <- h3_order_human %>%
  inner_join(
    annex_salience_conditions %>% select(pair_id, planned_salience_pair, planned_similarity, annex_condition),
    by = "pair_id"
  ) %>%
  filter(!is.na(planned_salience_pair), !is.na(planned_similarity))

h2d_condition_moderation_models <- bind_rows(
  safe_condition_lm(h1_condition_data, "human_value", "presentation_salience_pattern * planned_similarity", "H1 ipsative asymmetry by planned salience/similarity"),
  safe_condition_lm(h2_condition_data, "human_value", "presentation_salience_pattern * planned_similarity", "H2 ipsative salience by planned salience/similarity"),
  safe_condition_lm(h3_similarity_condition_data, "human_value", "planned_salience_pair * planned_similarity", "H3 Likert similarity by planned salience/similarity"),
  safe_condition_lm(h3_order_condition_data, "human_order_delta", "planned_salience_pair * planned_similarity", "H3 Likert order asymmetry by planned salience/similarity")
) %>%
  mutate(p_fdr = p.adjust(p.value, method = "BH"))

apa_table(
  h2d_condition_moderation_models %>%
    filter(term != "(Intercept)") %>%
    transmute(
      Analysis = analysis_block,
      Term = term,
      n,
      Estimate = apa_num(estimate, 3),
      SE = apa_num(std.error, 3),
      statistic = apa_num(statistic, 2),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table H2d-1. Planned salience/similarity conditions as moderators of human contrasts."
)
Table H2d-1. Planned salience/similarity conditions as moderators of human contrasts.
Analysis Term n Estimate SE statistic p q
H1 ipsative asymmetry by planned salience/similarity presentation_salience_patternlow_high 30 0.206 0.081 2.54 .019 .048
H1 ipsative asymmetry by planned salience/similarity presentation_salience_patternhigh_low 30 -0.580 0.071 -8.18 < .001 < .001
H1 ipsative asymmetry by planned salience/similarity presentation_salience_patternhigh_high 30 -0.321 0.061 -5.22 < .001 < .001
H1 ipsative asymmetry by planned salience/similarity planned_similarityhigh 30 -0.123 0.061 -2.01 .057 .123
H1 ipsative asymmetry by planned salience/similarity presentation_salience_patternlow_high:planned_similarityhigh 30 0.177 0.115 1.54 .138 .201
H1 ipsative asymmetry by planned salience/similarity presentation_salience_patternhigh_low:planned_similarityhigh 30 0.167 0.100 1.66 .110 .193
H1 ipsative asymmetry by planned salience/similarity presentation_salience_patternhigh_high:planned_similarityhigh 30 0.366 0.087 4.22 < .001 .001
H2 ipsative salience by planned salience/similarity presentation_salience_patternlow_high 30 -0.236 0.125 -1.88 .073 .143
H2 ipsative salience by planned salience/similarity presentation_salience_patternhigh_low 30 0.697 0.110 6.36 < .001 < .001
H2 ipsative salience by planned salience/similarity presentation_salience_patternhigh_high 30 0.417 0.095 4.39 < .001 < .001
H2 ipsative salience by planned salience/similarity planned_similarityhigh 30 0.176 0.095 1.86 .077 .143
H2 ipsative salience by planned salience/similarity presentation_salience_patternlow_high:planned_similarityhigh 30 -0.186 0.177 -1.05 .307 .358
H2 ipsative salience by planned salience/similarity presentation_salience_patternhigh_low:planned_similarityhigh 30 -0.235 0.155 -1.52 .144 .201
H2 ipsative salience by planned salience/similarity presentation_salience_patternhigh_high:planned_similarityhigh 30 -0.466 0.134 -3.48 .002 .006
H3 Likert similarity by planned salience/similarity planned_salience_pairhigh_low 30 -0.658 0.426 -1.54 .136 .201
H3 Likert similarity by planned salience/similarity planned_salience_pairhigh_high 30 0.115 0.426 0.27 .789 .789
H3 Likert similarity by planned salience/similarity planned_similarityhigh 30 2.442 0.426 5.73 < .001 < .001
H3 Likert similarity by planned salience/similarity planned_salience_pairhigh_low:planned_similarityhigh 30 1.432 0.603 2.37 .026 .061
H3 Likert similarity by planned salience/similarity planned_salience_pairhigh_high:planned_similarityhigh 30 0.916 0.603 1.52 .142 .201
H3 Likert order asymmetry by planned salience/similarity planned_salience_pairhigh_low 30 -0.054 0.112 -0.49 .631 .669
H3 Likert order asymmetry by planned salience/similarity planned_salience_pairhigh_high 30 -0.052 0.112 -0.47 .645 .669
H3 Likert order asymmetry by planned salience/similarity planned_similarityhigh 30 -0.138 0.112 -1.23 .230 .303
H3 Likert order asymmetry by planned salience/similarity planned_salience_pairhigh_low:planned_similarityhigh 30 0.167 0.158 1.06 .302 .358
H3 Likert order asymmetry by planned salience/similarity planned_salience_pairhigh_high:planned_similarityhigh 30 0.192 0.158 1.21 .238 .303
Show code
write_table(h2d_condition_moderation_models, "h2d_condition_moderation_models.csv")
Show code
h2d_h1_condition_metric_data <- join_metrics_to_behavior(h1_condition_data, pairwise_metric_values) %>%
  filter(is_asymmetry_metric(metric_name)) %>%
  mutate(
    analysis_block = "H1 ipsative asymmetry",
    condition_block = paste(as.character(presentation_salience_pattern), as.character(planned_similarity), sep = " | similarity=")
  )

h2d_h2_condition_metric_data <- join_metrics_to_behavior(h2_condition_data, pairwise_metric_values) %>%
  filter(is_asymmetry_metric(metric_name)) %>%
  mutate(
    analysis_block = "H2 ipsative salience",
    condition_block = paste(as.character(presentation_salience_pattern), as.character(planned_similarity), sep = " | similarity=")
  )

h2d_h3_order_condition_metric_data <- h3_order_metrics %>%
  inner_join(
    annex_salience_conditions %>% select(pair_id, planned_salience_pair, planned_similarity, annex_condition),
    by = "pair_id"
  ) %>%
  mutate(
    analysis_block = "H3 Likert order asymmetry",
    condition_block = paste(as.character(planned_salience_pair), as.character(planned_similarity), sep = " | similarity=")
  )

h2d_condition_metric_rank <- bind_rows(
  h2d_h1_condition_metric_data,
  h2d_h2_condition_metric_data,
  h2d_h3_order_condition_metric_data
) %>%
  filter(!is.na(condition_block)) %>%
  cor_by_group(
    group_cols = c("analysis_block", "condition_block", rank_group_cols),
    x = "metric_value",
    y = "human_value"
  )

h2d_best_metric_by_condition <- h2d_condition_metric_rank %>%
  filter(!is.na(estimate), n >= 3) %>%
  group_by(analysis_block, condition_block) %>%
  slice_max(order_by = abs_estimate, n = 1, with_ties = FALSE) %>%
  ungroup() %>%
  arrange(analysis_block, condition_block)

apa_table(
  h2d_best_metric_by_condition %>%
    transmute(
      Analysis = analysis_block,
      Condition = condition_block,
      Metric = metric_short,
      Family = metric_family,
      Object = coalesce(basis_object, object, state_type, ""),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table H2d-2. Best directional computational-human association within each salience/similarity condition."
)
Table H2d-2. Best directional computational-human association within each salience/similarity condition.
Analysis Condition Metric Family Object n Spearman rho p q
H1 ipsative asymmetry high_high | similarity=high Containment asymmetry Operator/density geometry P_concat_direct 5 1.00 < .001 < .001
H1 ipsative asymmetry high_high | similarity=low Containment asymmetry Operator/density geometry rho_fit_global_density 5 .90 .037 .198
H1 ipsative asymmetry high_low | similarity=high Containment asymmetry Operator/density geometry P_global 3 1.00 < .001 < .001
H1 ipsative asymmetry high_low | similarity=low Containment asymmetry Operator/density geometry P_concat_direct 3 1.00 < .001 < .001
H1 ipsative asymmetry low_low | similarity=high Containment asymmetry Operator/density geometry P_concat_direct 5 .90 .037 .198
H1 ipsative asymmetry low_low | similarity=low Containment asymmetry Operator/density geometry P_concat_direct 5 1.00 < .001 < .001
H2 ipsative salience high_high | similarity=high Containment asymmetry Operator/density geometry P_concat_direct 5 −.90 .037 .198
H2 ipsative salience high_high | similarity=low Containment asymmetry Operator/density geometry P_concat_direct 5 −.90 .037 .198
H2 ipsative salience high_low | similarity=high Containment asymmetry Operator/density geometry P_global 3 -1.00 < .001 < .001
H2 ipsative salience high_low | similarity=low Containment asymmetry Operator/density geometry P_concat_direct 3 -1.00 < .001 < .001
H2 ipsative salience low_low | similarity=high Containment asymmetry Operator/density geometry P_concat_direct 5 −.90 .037 .198
H2 ipsative salience low_low | similarity=low QSM asymmetry Sequential QSM S_global 5 -1.00 < .001 < .001
H3 Likert order asymmetry high_high | similarity=high Containment asymmetry Operator/density geometry rho_fit_global_density 5 −.90 .037 .198
H3 Likert order asymmetry high_high | similarity=low QSM asymmetry Sequential QSM S_global 5 .90 .037 .198
H3 Likert order asymmetry high_low | similarity=high Containment asymmetry Operator/density geometry projector_mixture_freq 5 1.00 < .001 < .001
H3 Likert order asymmetry high_low | similarity=low Containment asymmetry Operator/density geometry rho_global_density 5 .90 .037 .198
H3 Likert order asymmetry low_low | similarity=high Containment asymmetry Operator/density geometry P_concat_direct 5 -1.00 < .001 < .001
H3 Likert order asymmetry low_low | similarity=low Containment asymmetry Operator/density geometry P_global 5 1.00 < .001 < .001
Show code
write_table(h2d_condition_metric_rank, "h2d_condition_metric_rankings.csv")
write_table(h2d_best_metric_by_condition, "h2d_best_metric_by_condition.csv")
Show code
h2d_order_term <- h2d_condition_moderation_models %>%
  filter(analysis_block == "H3 Likert order asymmetry by planned salience/similarity", term != "(Intercept)", !str_detect(term, "model_not_estimable|model_error")) %>%
  arrange(p.value) %>%
  slice_head(n = 1)

h2d_best_coupling <- h2d_best_metric_by_condition %>%
  filter(!is.na(estimate)) %>%
  arrange(desc(abs_estimate)) %>%
  slice_head(n = 1)

order_sentence <- if (nrow(h2d_order_term) > 0) {
  paste0("The most informative H3 order-asymmetry condition term is `", h2d_order_term$term[[1]], "` (p ", apa_p(h2d_order_term$p.value[[1]]), "). ")
} else {
  "The H3 order-asymmetry condition model was not estimable with enough variation in the available cells. "
}

coupling_sentence <- if (nrow(h2d_best_coupling) > 0) {
  paste0(
    "The strongest within-condition computational-human coupling is found for ", h2d_best_coupling$analysis_block[[1]],
    " in the condition ", h2d_best_coupling$condition_block[[1]], ", with ", h2d_best_coupling$metric_short[[1]],
    " from ", h2d_best_coupling$metric_family[[1]], " reaching rho = ", apa_r(h2d_best_coupling$estimate[[1]]), ". "
  )
} else {
  "No condition-specific computational coupling was estimable. "
}

cat(
  "**Interpretation.** These condition-level analyses ask whether the planned salience manipulation also changes the expression of other effects. ",
  order_sentence,
  "Because several cells contain only five pairs, these models are better treated as a design audit than as decisive inference. ",
  coupling_sentence,
  "If unequal-salience cells show stronger directional containment effects than equal-salience cells, that supports the thesis interpretation that salience changes the diagnostic weighting of conceptual regions rather than merely adding a generic response bias.\n"
)

Interpretation. These condition-level analyses ask whether the planned salience manipulation also changes the expression of other effects. The most informative H3 order-asymmetry condition term is planned_similarityhigh (p .230). Because several cells contain only five pairs, these models are better treated as a design audit than as decisive inference. The strongest within-condition computational-human coupling is found for H1 ipsative asymmetry in the condition high_low | similarity=high, with Containment asymmetry from Operator/density geometry reaching rho = 1.00. If unequal-salience cells show stronger directional containment effects than equal-salience cells, that supports the thesis interpretation that salience changes the diagnostic weighting of conceptual regions rather than merely adding a generic response bias.

12 H3: Likert similarity and order asymmetry

12.1 Theoretical rationale

Likert similarity is psychologically different from ipsative forced choice. A rating scale encourages participants to compress a conceptual relation into a single graded number. That kind of task can be well approximated by average semantic proximity, which is exactly what a contour centroid represents. Therefore, a strong centroid-cosine baseline in H3 is not a failure of the quantum-like model. It means that explicit scalar similarity may be closer to a classical average-proximity judgment.

The more demanding prediction concerns order asymmetry. If the same pair receives different ratings depending on order, then directional subspace metrics should help. If order effects are weak or absent in the human data, then no computational metric should be expected to produce a strong confirmatory result. A weak H3b is therefore informative: the measurement format may suppress the asymmetry that appears under ipsative choice.

H3 is separated into two analyses. H3a asks whether metrics predict order-averaged explicit similarity ratings. H3b asks whether metrics predict the much smaller order asymmetry in those ratings.

12.2 H3a: order-averaged Likert similarity

Here the two presentation orders are averaged, so the dependent variable is intentionally symmetric. The best computational account should therefore be a symmetric similarity or reversed distance. This is the place where the classical centroid baseline is allowed to shine. Annoying, perhaps, but methodologically healthy.

Show code
h3_rating_top <- h3_rating_rank %>% slice_head(n = params$top_n_metrics)
h3_rating_family_best <- best_by_family(h3_rating_rank)
h3_rating_cosine_comp <- compare_quantum_vs_cosine(h3_rating_rank)

apa_table(
  h3_rating_family_best %>%
    transmute(
      Family = metric_family,
      Metric = metric_short,
      Representation = coalesce(basis_object, object, state_type, state_vector_source, ""),
      Variant = str_trunc(coalesce(variant_label, ""), 50),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 13. H3a best predictor within each metric family."
)
Table 13. H3a best predictor within each metric family.
Family Metric Representation Variant n Spearman rho p q
Classical vector baseline Angular distance, reversed contour_centroid classical 89 .51 < .001 < .001
Operator/density geometry a in b trace normalized rho_freq_density bayesian_gmm | g=gavish_donoho | l=gavish_donoho 89 .45 < .001 < .001
Subspace/projector geometry principal overlap trace S_global bayesian_gmm | g=profile_likelihood | l=profile... 89 .42 < .001 .001
Sequential QSM QSM p(A|B) S_global bayesian_gmm | g=profile_likelihood | l=profile... 89 .37 < .001 .008
Show code
apa_table(
  h3_rating_cosine_comp %>%
    transmute(
      `Comparison family` = comparison_family,
      Metric = metric_short,
      Family = metric_family,
      Representation = coalesce(basis_object, object, state_type, ""),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 14. H3a direct comparison between quantum-like predictors and classical cosine."
)
Table 14. H3a direct comparison between quantum-like predictors and classical cosine.
Comparison family Metric Family Representation n Spearman rho p q
Best classical cosine Centroid cosine Classical vector baseline contour_centroid 89 .51 < .001 < .001
Best quantum-like metric a in b trace normalized Operator/density geometry rho_freq_density 89 .45 < .001 < .001
Show code
plot_family_comparison(
  h3_rating_family_best,
  title = "H3a: best predictor by metric family",
  filename = "h3_rating_best_by_metric_family_v7.png"
)

Show code
h3_primary <- h3_rating_rank %>% slice_head(n = 1)
plot_metric_scatter(
  h3_similarity_metrics,
  h3_primary,
  title = "H3a primary metric for explicit similarity ratings",
  y_label = "Human order-averaged similarity rating",
  filename = "h3_rating_primary_metric_scatter_v7.png"
)

Show code
h3_best <- h3_rating_rank %>% slice_head(n = 1)
h3_cos <- h3_rating_cosine_comp %>% filter(comparison_family == "Best classical cosine") %>% slice_head(n = 1)
cat(
  "**Interpretation.** For explicit Likert similarity, the best overall metric is ", h3_best$metric_short[[1]],
  " (", h3_best$metric_family[[1]], "), rho = ", apa_r(h3_best$estimate[[1]]),
  ". The best classical cosine baseline gives rho = ", apa_r(h3_cos$estimate[[1]]),
  ". If cosine is competitive or superior here, this is theoretically useful rather than embarrassing: explicit scalar similarity is exactly where centroid-based vector semantics should perform well. The quantum-like model is expected to add more for directionality, context, and relational structure.\n"
)

Interpretation. For explicit Likert similarity, the best overall metric is Angular distance, reversed ( Classical vector baseline ), rho = .51 . The best classical cosine baseline gives rho = .51 . If cosine is competitive or superior here, this is theoretically useful rather than embarrassing: explicit scalar similarity is exactly where centroid-based vector semantics should perform well. The quantum-like model is expected to add more for directionality, context, and relational structure.

12.3 H3b: order asymmetry in Likert ratings

Here the dependent variable is the difference between orders. Because the human order effect is small, this block is treated as exploratory. A metric can correlate with the small residual order effect, but the interpretation should remain cautious unless the human asymmetry itself is reliable.

Show code
h3_order_top <- h3_order_rank %>% slice_head(n = params$top_n_metrics)

apa_table(
  h3_order_test %>%
    transmute(
      n,
      `Mean direct − reverse` = apa_num(mean_delta, 3),
      `Median |delta|` = apa_num(median_abs_delta, 3),
      t = apa_num(statistic, 2),
      df = apa_num(df, 1),
      p = apa_p(p.value)
    ),
  caption = "Table 15. Human order asymmetry in Likert ratings."
)
Table 15. Human order asymmetry in Likert ratings.
n Mean direct − reverse Median |delta| t df p
89 -0.014 0.133 -0.81 88.0 .422
Show code
apa_table(
  h3_order_top %>%
    transmute(
      Metric = metric_short,
      Family = metric_family,
      Representation = coalesce(basis_object, object, state_type, ""),
      Variant = str_trunc(coalesce(variant_label, ""), 50),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 16. Exploratory predictors of H3 order asymmetry."
)
Table 16. Exploratory predictors of H3 order asymmetry.
Metric Family Representation Variant n Spearman rho p q
Containment asymmetry Operator/density geometry P_concat_direct bayesian_gmm | g=gavish_donoho | l=gavish_donoho 89 .32 .002 .029
Containment asymmetry Operator/density geometry P_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 89 .32 .002 .029
Containment asymmetry Subspace/projector geometry S_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 89 .32 .002 .029
qsm neutral asymmetry a minus b Subspace/projector geometry S_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 89 .32 .002 .029
Containment asymmetry Operator/density geometry projector_mixture_freq bayesian_gmm | g=horn_wishart | l=horn_wishart 89 .31 .003 .029
Containment asymmetry Operator/density geometry projector_mixture_fit_global bayesian_gmm | g=horn_wishart | l=horn_wishart 89 .31 .003 .029
Containment asymmetry Operator/density geometry P_concat_direct gmm_bic | g=gavish_donoho | l=gavish_donoho 89 .31 .003 .029
Containment asymmetry Operator/density geometry P_lowdin gmm_bic | g=gavish_donoho | l=gavish_donoho 89 .31 .003 .029
Containment asymmetry Subspace/projector geometry S_lowdin gmm_bic | g=gavish_donoho | l=gavish_donoho 89 .31 .003 .029
qsm neutral asymmetry a minus b Subspace/projector geometry S_lowdin gmm_bic | g=gavish_donoho | l=gavish_donoho 89 .31 .003 .029
Containment asymmetry Operator/density geometry P_concat_direct bayesian_gmm | g=horn_wishart | l=horn_wishart 89 .31 .003 .029
Containment asymmetry Operator/density geometry P_lowdin bayesian_gmm | g=horn_wishart | l=horn_wishart 89 .31 .003 .029
Show code
h3_order_best <- h3_order_top %>% slice_head(n = 1)
cat(
  "**Interpretation.** The human order effect is small: mean direct-minus-reverse delta = ",
  apa_num(h3_order_test$mean_delta[[1]], 3), " (p ", apa_p(h3_order_test$p.value[[1]]), "). ",
  "The best computational association with order asymmetry is ", h3_order_best$metric_short[[1]],
  ", rho = ", apa_r(h3_order_best$estimate[[1]]), ". Because the behavioral effect itself is weak, this block should be treated as exploratory rather than confirmatory.\n"
)

Interpretation. The human order effect is small: mean direct-minus-reverse delta = -0.014 (p .422 ). The best computational association with order asymmetry is Containment asymmetry , rho = .32 . Because the behavioral effect itself is weak, this block should be treated as exploratory rather than confirmatory.

13 H4: triangle and multiplicative triangle structure

13.1 Theoretical rationale

Triangle analyses test whether conceptual similarity behaves like a classical metric space. If human judgments violate additive or multiplicative triangle expectations, then a simple Euclidean interpretation is incomplete. The quantum-like model offers several alternatives: projector distances, Bures-like distances, and fidelity-like similarities between normalized conceptual structures.

The main theoretical distinction is between distance residuals and similarity-product residuals. Additive triangle residuals evaluate whether one conceptual distance exceeds the path through a third concept. Multiplicative residuals evaluate whether direct similarity is lower than expected from the product of two indirect similarities. The latter is especially relevant because it can reveal non-classical relational structure even when additive geometry looks well behaved.

13.2 Methodological choices for H4

The notebook uses only the triplets present in the human design and evaluates both continuous residuals and binary violation detection. This matters because a model can capture the rank ordering of residuals without correctly classifying the small number of actual violations. Treating these as separate outcomes prevents a base-rate accuracy mirage, that charming statistical swamp.

H4 evaluates whether the geometry of the computational model captures triplet structure. The key analyses are continuous residual correlations. Binary violations are also summarized, but they are less informative when violations are rare.

Show code
apa_table(human_triplet_summary, caption = "Table 17. Human triplet residual summary.")
Table 17. Human triplet residual summary.
n_contrasts n_unordered_triplets additive_violations mti_lower_than_product mean_triangle_residual mean_mti_residual
66 22 0 7 -0.593 0.248
Show code
h4_distance_top <- h4_distance_rank %>% slice_head(n = params$top_n_metrics)
h4_mti_top <- h4_mti_rank %>% slice_head(n = params$top_n_metrics)

apa_table(
  h4_distance_top %>%
    transmute(
      Metric = metric_short,
      Object = basis_object,
      Variant = str_trunc(variant_label, 50),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 18. H4 additive triangle residual correlations."
)
Table 18. H4 additive triangle residual correlations.
Metric Object Variant n Spearman rho p q
Bures distance, reversed S_global bayesian_gmm | g=horn_wishart | l=horn_wishart 66 .49 < .001 < .001
Bures distance, reversed S_global gmm_bic | g=horn_wishart | l=horn_wishart 66 .49 < .001 < .001
Bures distance, reversed S_global bayesian_gmm | g=profile_likelihood | l=profile... 66 .49 < .001 < .001
Bures distance, reversed S_global gmm_bic | g=profile_likelihood | l=profile_like... 66 .49 < .001 < .001
Chordal distance, reversed S_global bayesian_gmm | g=horn_wishart | l=horn_wishart 66 .45 < .001 < .001
Chordal distance, reversed S_global gmm_bic | g=horn_wishart | l=horn_wishart 66 .45 < .001 < .001
Chordal distance, reversed S_global bayesian_gmm | g=profile_likelihood | l=profile... 66 .45 < .001 .001
Chordal distance, reversed S_global gmm_bic | g=profile_likelihood | l=profile_like... 66 .45 < .001 .001
Bures distance, reversed S_global bayesian_gmm | g=permutation_pa | l=permutation_pa 66 .42 < .001 .002
Bures distance, reversed S_global gmm_bic | g=permutation_pa | l=permutation_pa 66 .42 < .001 .002
Chordal distance, reversed S_global bayesian_gmm | g=permutation_pa | l=permutation_pa 66 .42 < .001 .002
Chordal distance, reversed S_global gmm_bic | g=permutation_pa | l=permutation_pa 66 .42 < .001 .002
Show code
apa_table(
  h4_mti_top %>%
    transmute(
      Metric = metric_short,
      Object = basis_object,
      Variant = str_trunc(variant_label, 50),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr)
    ),
  caption = "Table 19. H4 multiplicative triangle residual correlations."
)
Table 19. H4 multiplicative triangle residual correlations.
Metric Object Variant n Spearman rho p q
Density fidelity S_global bayesian_gmm | g=profile_likelihood | l=profile... 66 .55 < .001 < .001
Density fidelity S_global gmm_bic | g=profile_likelihood | l=profile_like... 66 .55 < .001 < .001
Projector overlap S_global bayesian_gmm | g=profile_likelihood | l=profile... 66 .52 < .001 < .001
Projector overlap S_global gmm_bic | g=profile_likelihood | l=profile_like... 66 .52 < .001 < .001
Density fidelity S_global bayesian_gmm | g=horn_wishart | l=horn_wishart 66 .51 < .001 < .001
Density fidelity S_global gmm_bic | g=horn_wishart | l=horn_wishart 66 .51 < .001 < .001
Projector overlap S_global bayesian_gmm | g=horn_wishart | l=horn_wishart 66 .44 < .001 .001
Projector overlap S_global gmm_bic | g=horn_wishart | l=horn_wishart 66 .44 < .001 .001
Density fidelity S_lowdin bayesian_gmm | g=horn_wishart | l=horn_wishart 66 .42 < .001 .002
Density fidelity S_global bayesian_gmm | g=permutation_pa | l=permutation_pa 66 .42 < .001 .002
Density fidelity S_global gmm_bic | g=permutation_pa | l=permutation_pa 66 .42 < .001 .002
Projector overlap S_global bayesian_gmm | g=permutation_pa | l=permutation_pa 66 .41 < .001 .002
Show code
apa_table(
  mti_classification %>%
    slice_head(n = params$top_n_metrics) %>%
    transmute(
      Metric = metric_short_label(similarity_metric),
      Object = basis_object,
      Variant = str_trunc(variant_short(variant), 50),
      n,
      Accuracy = apa_num(accuracy, 2),
      Precision = apa_num(precision, 2),
      Recall = apa_num(recall, 2),
      F1 = apa_num(f1, 2)
    ),
  caption = "Table 20. H4 MTI lower-than-product classification performance."
)
Table 20. H4 MTI lower-than-product classification performance.
Metric Object Variant n Accuracy Precision Recall F1
Mean cos² principal angles S_lowdin bayesian_gmm | g=permutation_pa | l=permutation_pa 66 0.88 0.40 0.29 0.33
Mean cos² principal angles S_global bayesian_gmm | g=permutation_pa | l=permutation_pa 66 0.89 0.50 0.14 0.22
Mean cos² principal angles S_global gmm_bic | g=permutation_pa | l=permutation_pa 66 0.89 0.50 0.14 0.22
Mean cos² principal angles S_lowdin bayesian_gmm | g=profile_likelihood | l=profile... 66 0.86 0.25 0.14 0.18
Mean cos² principal angles S_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 66 0.85 0.20 0.14 0.17
Mean cos² principal angles S_lowdin bayesian_gmm | g=horn_wishart | l=horn_wishart 66 0.85 0.20 0.14 0.17
Mean cos² principal angles S_lowdin gmm_bic | g=gavish_donoho | l=gavish_donoho 66 0.73 0.08 0.14 0.10
Density fidelity S_global bayesian_gmm | g=gavish_donoho | l=gavish_donoho 66 0.89 0.00 0.00 0.00
Mean cos² principal angles S_global bayesian_gmm | g=gavish_donoho | l=gavish_donoho 66 0.89 0.00 0.00 0.00
Projector overlap S_global bayesian_gmm | g=gavish_donoho | l=gavish_donoho 66 0.89 0.00 0.00 0.00
Density fidelity S_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 66 0.89 0.00 0.00 0.00
Projector overlap S_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 66 0.89 0.00 0.00 0.00
Show code
plot_ranked_correlations(
  h4_distance_top,
  title = "H4 additive triangle: residual correlations",
  subtitle = "Continuous residuals are more informative than rare binary violations.",
  n = params$top_n_plots,
  filename = "h4_additive_triangle_top_correlations_v7.png"
)

Show code
plot_ranked_correlations(
  h4_mti_top,
  title = "H4 multiplicative triangle: residual correlations",
  subtitle = "Positive correlation means computational MTI residuals track human MTI residuals.",
  n = params$top_n_plots,
  filename = "h4_mti_top_correlations_v7.png"
)

Show code
h4_dist_primary <- h4_distance_top %>% slice_head(n = 1)
h4_dist_primary_data <- triplet_distance_human %>%
  filter(variant == h4_dist_primary$variant[[1]], basis_object == h4_dist_primary$basis_object[[1]], distance_metric == h4_dist_primary$distance_metric[[1]])

h4_dist_scatter <- ggplot(h4_dist_primary_data, aes(x = triangle_residual_ac_minus_ab_bc, y = human_triangle_residual)) +
  geom_point(size = 2.2, alpha = .80) +
  geom_smooth(method = "lm", se = TRUE, linewidth = .75) +
  labs(
    title = "H4 additive triangle: primary residual metric",
    subtitle = paste0(h4_dist_primary$metric_short[[1]], "; rho = ", apa_r(h4_dist_primary$estimate[[1]])),
    x = "Computational triangle residual",
    y = "Human triangle residual"
  ) +
  theme_apa7()

h4_dist_scatter

Show code
save_plot(h4_dist_scatter, "h4_additive_primary_residual_scatter_v7.png")
Show code
h4_d_best <- h4_distance_top %>% slice_head(n = 1)
h4_m_best <- h4_mti_top %>% slice_head(n = 1)
cat(
  "**Interpretation.** Additive triangle structure is best captured by ", h4_d_best$metric_short[[1]],
  " with rho = ", apa_r(h4_d_best$estimate[[1]]), ". Multiplicative triangle structure is best captured by ",
  h4_m_best$metric_short[[1]], " with rho = ", apa_r(h4_m_best$estimate[[1]]), ". ",
  "The classification table should be read cautiously because lower-than-product violations are sparse; residual correlations are the more stable evidence.\n"
)

Interpretation. Additive triangle structure is best captured by Bures distance, reversed with rho = .49 . Multiplicative triangle structure is best captured by Density fidelity with rho = .55 . The classification table should be read cautiously because lower-than-product violations are sparse; residual correlations are the more stable evidence.

14 H6: diagnosticity

14.1 Theoretical rationale

Diagnosticity is the hardest block because the relevant comparison is not just between two isolated concepts. The human task introduces a context or competing alternative that changes which features are useful for judgment. In the thesis, three computational routes are relevant: sequential projection, contextualized similarity, and perplexity-inhibited similarity.

The static pairwise analyses ask whether diagnosticity can be approximated without explicit context. The contextualized analyses test whether the context acts as a coordinate frame or Lüders-style filter. The perplexity analyses test whether a plausible competing pair activates shared properties that should be removed before comparing the initial concept with each target. These mechanisms are not interchangeable. If one succeeds and another fails, the theoretical interpretation changes.

H6 is analyzed three ways: static pairwise metrics, contextualized metrics, and perplexity-inhibited metrics. The static analysis asks whether diagnostic judgments can be approximated without explicit context. The contextualized and perplexity analyses test mechanisms closer to the thesis model.

14.2 H6a: static pairwise diagnostic metrics

Static pairwise metrics deliberately ignore the explicit context. They are useful as a baseline: if they perform well, diagnosticity may partly reduce to pre-existing pairwise structure. If they perform poorly relative to contextualized metrics, then the context mechanism is doing real explanatory work.

Show code
h6_static_top <- h6_static_rank %>%
  group_by(hypothesis) %>%
  slice_max(order_by = abs_estimate, n = params$top_n_metrics, with_ties = FALSE) %>%
  ungroup()

h6_static_family_best <- h6_static_rank %>%
  mutate(metric_family = metric_family_from_source(metric_source)) %>%
  group_by(hypothesis, metric_family) %>%
  slice_max(order_by = abs_estimate, n = 1, with_ties = FALSE) %>%
  ungroup()

apa_table(
  h6_static_family_best %>%
    transmute(
      Hypothesis = hypothesis,
      Family = metric_family,
      Metric = metric_short,
      Representation = coalesce(basis_object, object, state_type, state_vector_source, ""),
      Variant = str_trunc(coalesce(variant_label, ""), 50),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr_within_hypothesis)
    ),
  caption = "Table 21. H6 static diagnosticity: best predictor within each metric family."
)
Table 21. H6 static diagnosticity: best predictor within each metric family.
Hypothesis Family Metric Representation Variant n Spearman rho p q
H6_diagnosticity_choice_full Classical vector baseline Angular distance, reversed contour_centroid classical 66 .28 .024 .775
H6_diagnosticity_choice_full Operator/density geometry frobenius distance rho_fit_global_density gmm_bic | g=profile_likelihood | l=profile_like... 66 .40 < .001 .235
H6_diagnosticity_choice_full Sequential QSM QSM p(B|A) S_global bayesian_gmm | g=profile_likelihood | l=profile... 66 .16 .187 .932
H6_diagnosticity_choice_full Subspace/projector geometry containment a to b S_lowdin bayesian_gmm | g=profile_likelihood | l=profile... 66 .22 .072 .932
H6_diagnosticity_rating Classical vector baseline Angular distance, reversed contour_centroid classical 44 .24 .119 .495
H6_diagnosticity_rating Operator/density geometry Containment asymmetry rho_freq_density gmm_bic | g=gavish_donoho | l=gavish_donoho 44 −.69 < .001 < .001
H6_diagnosticity_rating Sequential QSM QSM p(B|A) S_global bayesian_gmm | g=profile_likelihood | l=profile... 44 .50 < .001 .036
H6_diagnosticity_rating Subspace/projector geometry max angle deg S_lowdin bayesian_gmm | g=permutation_pa | l=permutation_pa 44 .56 < .001 .013
Show code
h6_static_plot <- h6_static_family_best %>%
  mutate(
    label = paste0(str_replace(hypothesis, "H6_diagnosticity_", ""), "\n", metric_family),
    label = fct_reorder(label, abs_estimate)
  ) %>%
  ggplot(aes(x = label, y = estimate)) +
  geom_hline(yintercept = 0, linewidth = .35) +
  geom_col(width = .72) +
  coord_flip() +
  scale_y_continuous(limits = c(-1, 1), breaks = seq(-1, 1, .25)) +
  labs(
    title = "H6 static diagnosticity: best metric by family",
    subtitle = "Static pairwise metrics ignore explicit context, so this is a strict baseline.",
    x = NULL,
    y = "Spearman rho"
  ) +
  theme_apa7()

h6_static_plot

Show code
save_plot(h6_static_plot, "h6_static_best_by_family_v7.png", width = 8.5, height = 5.5)

14.3 H6b: contextualized similarity

Contextualized similarity evaluates concepts after they have been represented with respect to the contextual subspace. In vector form, this means comparing projected centroid states inside the context basis. In density form, it means filtering each conceptual density through the context projector and renormalizing. The density route is closer to a Lüders-style update, while the vector route is closer to contextual coordinates.

Show code
h6_context_top <- h6_context_rank %>%
  group_by(hypothesis) %>%
  slice_max(order_by = abs_estimate, n = params$top_n_metrics, with_ties = FALSE) %>%
  ungroup()

h6_context_family_best <- h6_context_rank %>%
  group_by(hypothesis, metric_family) %>%
  slice_max(order_by = abs_estimate, n = 1, with_ties = FALSE) %>%
  ungroup()

apa_table(
  h6_context_family_best %>%
    transmute(
      Hypothesis = hypothesis,
      Family = metric_family,
      Metric = metric_short,
      Object = coalesce(basis_object, density_object, state_vector_source, ""),
      Variant = str_trunc(coalesce(variant_label, ""), 50),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr_within_hypothesis)
    ),
  caption = "Table 22. H6 contextualized similarity: best predictor within each contextual family."
)
Table 22. H6 contextualized similarity: best predictor within each contextual family.
Hypothesis Family Metric Object Variant n Spearman rho p q
H6_diagnosticity_choice_full Contextualized vector Contextualized vector cosine S_global bayesian_gmm | g=gavish_donoho | l=gavish_donoho 212 .29 < .001 < .001
H6_diagnosticity_choice_full Lüders contextualized density Contextual density HS inner S_global bayesian_gmm | g=profile_likelihood | l=profile... 212 .36 < .001 < .001
Show code
h6_context_plot <- h6_context_family_best %>%
  mutate(
    label = paste0(str_replace(hypothesis, "H6_diagnosticity_", ""), "\n", metric_family),
    label = fct_reorder(label, abs_estimate)
  ) %>%
  ggplot(aes(x = label, y = estimate)) +
  geom_hline(yintercept = 0, linewidth = .35) +
  geom_col(width = .72) +
  coord_flip() +
  scale_y_continuous(limits = c(-1, 1), breaks = seq(-1, 1, .25)) +
  labs(
    title = "H6 contextualized metrics",
    subtitle = "Vector contextualization and Lüders density contextualization are shown separately.",
    x = NULL,
    y = "Spearman rho"
  ) +
  theme_apa7()

h6_context_plot

Show code
save_plot(h6_context_plot, "h6_contextualized_best_by_family_v7.png", width = 8.5, height = 5)

14.4 H6c: perplexity-inhibited similarity

Perplexity-inhibited similarity implements the idea that the most plausible competing pair may share properties that become unhelpful or confusing for the target judgment. The model estimates a soft intersection for that pair and removes this shared structure from the initial concept before comparing it with each target. A strong negative result is not automatically a failure: it may indicate that the residual representation captures competition but maps onto the opposite behavioral choice rule.

Show code
h6_perplexity_top <- h6_perplexity_rank %>%
  group_by(hypothesis) %>%
  slice_max(order_by = abs_estimate, n = params$top_n_metrics, with_ties = FALSE) %>%
  ungroup()

h6_perplexity_family_best <- h6_perplexity_rank %>%
  group_by(hypothesis, metric_family) %>%
  slice_max(order_by = abs_estimate, n = 1, with_ties = FALSE) %>%
  ungroup()

apa_table(
  h6_perplexity_family_best %>%
    transmute(
      Hypothesis = hypothesis,
      Family = metric_family,
      Metric = metric_short,
      Object = coalesce(basis_object, density_object, ""),
      Variant = str_trunc(coalesce(variant_label, ""), 50),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr_within_hypothesis)
    ),
  caption = "Table 23. H6 perplexity-inhibited similarity predictors."
)
Table 23. H6 perplexity-inhibited similarity predictors.
Hypothesis Family Metric Object Variant n Spearman rho p q
H6_diagnosticity_choice_full Perplexity-inhibited diagnostic perplexity residual projector to target containment S_global bayesian_gmm | g=horn_wishart | l=horn_wishart 212 −.26 < .001 .007
Show code
h6_perplexity_plot <- h6_perplexity_top %>%
  mutate(label = fct_reorder(str_wrap(metric_short, 32), abs_estimate)) %>%
  ggplot(aes(x = label, y = estimate)) +
  geom_hline(yintercept = 0, linewidth = .35) +
  geom_col(width = .72) +
  coord_flip() +
  facet_wrap(~ str_replace(hypothesis, "H6_diagnosticity_", ""), scales = "free_y") +
  scale_y_continuous(limits = c(-1, 1), breaks = seq(-1, 1, .5)) +
  labs(
    title = "H6 perplexity-inhibited metrics",
    subtitle = "Negative signs can be meaningful: inhibition may reverse target preference depending on the chosen residual representation.",
    x = NULL,
    y = "Spearman rho"
  ) +
  theme_apa7()

h6_perplexity_plot

Show code
save_plot(h6_perplexity_plot, "h6_perplexity_top_correlations_v7.png", width = 9, height = 5.5)

14.5 H6d: diagnostic choice prediction accuracy

The accuracy analysis turns metric differences into pairwise choices. This is stricter than a correlation with graded human proportions. A model can track the continuous preference gradient yet fail to classify the winner for individual diagnostic pairs, especially when human choices are noisy or close to chance.

Show code
h6_choice_top <- h6_choice_accuracy %>%
  group_by(hypothesis, diagnostic_choice_set, metric_family_label) %>%
  slice_max(order_by = accuracy, n = 1, with_ties = FALSE) %>%
  ungroup() %>%
  arrange(hypothesis, diagnostic_choice_set, desc(accuracy))

apa_table(
  h6_choice_top %>%
    transmute(
      Hypothesis = hypothesis,
      `Choice set` = diagnostic_choice_set,
      Family = metric_family_label,
      Metric = metric_short_label(metric),
      Variant = str_trunc(variant_short(variant), 50),
      n,
      Accuracy = apa_num(accuracy, 2),
      `Mean |preference|` = apa_num(mean_abs_preference, 3)
    ),
  caption = "Table 24. H6 diagnostic pairwise choice prediction accuracy."
)
Table 24. H6 diagnostic pairwise choice prediction accuracy.
Hypothesis Choice set Family Metric Variant n Accuracy Mean |preference|
H6_diagnosticity_choice_full choice_candidates_only Lüders contextualized density Bures distance, reversed bayesian_gmm | g=horn_wishart | l=horn_wishart 22 0.64 0.109
H6_diagnosticity_choice_full choice_candidates_only perplexity_inhibited_similarity perplexity residual density to target containment bayesian_gmm | g=permutation_pa | l=permutation_pa 22 0.59 0.198
H6_diagnosticity_choice_full choice_candidates_only Contextualized vector Contextualized vector cosine bayesian_gmm | g=profile_likelihood | l=profile... 22 0.55 0.039
H6_diagnosticity_choice_full choice_full_with_context Lüders contextualized density Bures distance, reversed bayesian_gmm | g=gavish_donoho | l=gavish_donoho 66 0.64 0.094
H6_diagnosticity_choice_full choice_full_with_context perplexity_inhibited_similarity Bures distance, reversed bayesian_gmm | g=gavish_donoho | l=gavish_donoho 66 0.62 0.112
H6_diagnosticity_choice_full choice_full_with_context Contextualized vector Contextualized vector cosine bayesian_gmm | g=profile_likelihood | l=profile... 66 0.56 0.031
H6_diagnosticity_choice_full unknown Lüders contextualized density Bures distance, reversed bayesian_gmm | g=horn_wishart | l=horn_wishart 76 0.62 0.112
H6_diagnosticity_choice_full unknown Contextualized vector Contextualized vector cosine bayesian_gmm | g=profile_likelihood | l=profile... 76 0.61 0.037
H6_diagnosticity_choice_full unknown perplexity_inhibited_similarity perplexity residual density to target containment bayesian_gmm | g=permutation_pa | l=permutation_pa 76 0.53 0.185
Show code
h6_choice_plot <- h6_choice_top %>%
  mutate(label = fct_reorder(paste0(diagnostic_choice_set, "\n", metric_family_label), accuracy)) %>%
  ggplot(aes(x = label, y = accuracy)) +
  geom_hline(yintercept = .50, linetype = "dashed", linewidth = .35) +
  geom_col(width = .72) +
  coord_flip() +
  scale_y_continuous(limits = c(0, 1), breaks = seq(0, 1, .10), labels = percent_format(accuracy = 1)) +
  labs(
    title = "H6 diagnostic choice prediction accuracy",
    subtitle = "Dashed line marks chance-level pairwise accuracy.",
    x = NULL,
    y = "Accuracy"
  ) +
  theme_apa7()

h6_choice_plot

Show code
save_plot(h6_choice_plot, "h6_diagnostic_choice_prediction_accuracy_v7.png", width = 8.5, height = 5)
Show code
h6_static_best <- h6_static_rank %>% group_by(hypothesis) %>% slice_head(n = 1) %>% ungroup()
h6_context_best <- h6_context_rank %>% group_by(hypothesis) %>% slice_head(n = 1) %>% ungroup()
h6_perp_best <- h6_perplexity_rank %>% group_by(hypothesis) %>% slice_head(n = 1) %>% ungroup()
cat(
  "**Interpretation.** Diagnosticity is the least reducible block. Static metrics provide a useful baseline, especially for ratings, but explicit context mechanisms are needed to evaluate the thesis-level account. ",
  "Contextualized density metrics usually test whether the context acts as a projection/filter. Perplexity metrics test whether removing shared properties of a plausible competing pair changes target similarity. If perplexity shows strong but negative correlations, this should not be hidden under the rug like a bad residual; it may indicate that the residual-state definition is capturing competition but with reversed decision mapping.\n"
)

Interpretation. Diagnosticity is the least reducible block. Static metrics provide a useful baseline, especially for ratings, but explicit context mechanisms are needed to evaluate the thesis-level account. Contextualized density metrics usually test whether the context acts as a projection/filter. Perplexity metrics test whether removing shared properties of a plausible competing pair changes target similarity. If perplexity shows strong but negative correlations, this should not be hidden under the rug like a bad residual; it may indicate that the residual-state definition is capturing competition but with reversed decision mapping.

14.6 H6e: diagnostic context as a priming-like condition

The thesis does not include a separate priming experiment among the Phase 2 instruments. The experimental structure closest to priming is the diagnosticity task: a context or distractor is inserted into the comparison set, and that context should change which features become diagnostic. In that sense, diagnosticity is a controlled contextual manipulation rather than a generic pairwise similarity judgment.

This section therefore asks a more specific question: does the relation between the context/distractor and each candidate target predict the human diagnostic response? If so, the context is acting like a prime: it changes the accessibility or diagnostic value of features before the participant evaluates the initial-target relation.

Two quantities are tested:

  1. Context-target similarity. Is the candidate more likely to be chosen/rated highly when it is more similar to the context?
  2. Context-minus-initial similarity. Is the candidate favored when it is more aligned with the context than with the initial concept, or vice versa?

The second contrast is especially useful because diagnosticity is not just ordinary relatedness. It is about how a contextual item changes the comparison space.

Show code
diagnostic_design_overview <- diagnostic_design

diagnostic_design_overview$design_type_safe <- if ("design_type" %in% names(diagnostic_design_overview)) as.character(diagnostic_design_overview$design_type) else NA_character_
diagnostic_design_overview$response_type_safe <- if ("response_type" %in% names(diagnostic_design_overview)) as.character(diagnostic_design_overview$response_type) else NA_character_
diagnostic_design_overview$condition_safe <- if ("condition" %in% names(diagnostic_design_overview)) as.character(diagnostic_design_overview$condition) else NA_character_
diagnostic_design_overview$context_safe <- if ("context" %in% names(diagnostic_design_overview)) as.character(diagnostic_design_overview$context) else NA_character_
diagnostic_design_overview$initial_safe <- if ("initial" %in% names(diagnostic_design_overview)) as.character(diagnostic_design_overview$initial) else NA_character_
diagnostic_design_overview$target_safe <- if ("target" %in% names(diagnostic_design_overview)) as.character(diagnostic_design_overview$target) else NA_character_

diagnostic_condition_overview <- diagnostic_design_overview %>%
  mutate(
    diagnostic_condition_family = case_when(
      str_detect(coalesce(design_type_safe, response_type_safe, condition_safe, ""), "choice_full|full_with_context") ~ "choice with context option",
      str_detect(coalesce(design_type_safe, response_type_safe, condition_safe, ""), "choice_candidates") ~ "choice candidates only",
      str_detect(coalesce(design_type_safe, response_type_safe, condition_safe, ""), "rating_candidates_plus_context") ~ "rating candidates plus context",
      str_detect(coalesce(design_type_safe, response_type_safe, condition_safe, ""), "rating_candidates") ~ "rating candidates only",
      TRUE ~ "diagnostic row"
    )
  ) %>%
  group_by(diagnostic_condition_family, context_safe) %>%
  summarise(
    n_rows = n(),
    n_initials = n_distinct(initial_safe, na.rm = TRUE),
    n_targets = n_distinct(target_safe, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(diagnostic_condition_family, context_safe)

apa_table(
  diagnostic_condition_overview %>%
    transmute(
      `Diagnostic condition` = diagnostic_condition_family,
      Context = context_safe,
      `n rows` = n_rows,
      `n initials` = n_initials,
      `n targets` = n_targets
    ),
  caption = "Table H6e-1. Diagnostic context/condition overview."
)
Table H6e-1. Diagnostic context/condition overview.
Diagnostic condition Context n rows n initials n targets
diagnostic row alemania 10 1 3
diagnostic row belgica 10 1 4
diagnostic row bulgaria 10 1 3
diagnostic row chile 10 1 4
diagnostic row costa_rica 10 1 3
diagnostic row cuba 10 1 3
diagnostic row egipto 10 1 3
diagnostic row espana 20 2 5
diagnostic row francia 10 1 3
diagnostic row honduras 10 1 3
diagnostic row irak 10 1 3
diagnostic row irlanda 10 1 3
diagnostic row italia 10 1 3
diagnostic row japon 10 1 3
diagnostic row mongolia 10 1 3
diagnostic row noruega 10 1 3
diagnostic row nueva_zelanda 10 1 4
diagnostic row paises_bajos 10 1 3
diagnostic row polonia 10 1 3
diagnostic row portugal 10 1 3
diagnostic row rusia 10 1 3
Show code
write_table(diagnostic_condition_overview, "h6e_diagnostic_condition_overview.csv")
Show code
h6_pair_metric_lookup <- pairwise_metric_values %>%
  filter(!is_asymmetry_metric(metric_name)) %>%
  mutate(metric_value = similarity_orient_value(metric_name, value)) %>%
  distinct(metric_id, pair_id, .keep_all = TRUE) %>%
  select(any_of(c(
    "metric_id", "metric_source", "metric_family", "metric_name", "metric_short", "variant", "variant_label",
    "cluster_method", "global_k_method", "local_k_method", "basis_object", "object", "state_vector_source", "state_type",
    "pair_id", "metric_value"
  )))

h6_prime_option_rows <- diag_human_rows %>%
  mutate(
    context_target_pair_id = canonical_pair(context, target),
    initial_target_pair_id = canonical_pair(initial, target)
  )

h6_context_target_lookup <- h6_pair_metric_lookup %>%
  rename(context_target_pair_id = pair_id, context_target_metric = metric_value)

h6_initial_target_lookup <- h6_pair_metric_lookup %>%
  select(metric_id, pair_id, metric_value) %>%
  rename(initial_target_pair_id = pair_id, initial_target_metric = metric_value)

h6_prime_like_long <- h6_prime_option_rows %>%
  inner_join(h6_context_target_lookup, by = "context_target_pair_id") %>%
  inner_join(h6_initial_target_lookup, by = c("metric_id", "initial_target_pair_id")) %>%
  mutate(
    context_minus_initial_similarity = context_target_metric - initial_target_metric,
    abs_context_minus_initial_similarity = abs(context_minus_initial_similarity)
  )

h6_prime_context_target_rank <- cor_by_group(
  h6_prime_like_long,
  group_cols = c("hypothesis", rank_group_cols),
  x = "context_target_metric",
  y = "human_value"
) %>%
  mutate(priming_predictor = "context_target_similarity")

h6_prime_relative_rank <- cor_by_group(
  h6_prime_like_long,
  group_cols = c("hypothesis", rank_group_cols),
  x = "context_minus_initial_similarity",
  y = "human_value"
) %>%
  mutate(priming_predictor = "context_minus_initial_similarity")

h6_prime_abs_relative_rank <- cor_by_group(
  h6_prime_like_long,
  group_cols = c("hypothesis", rank_group_cols),
  x = "abs_context_minus_initial_similarity",
  y = "human_value"
) %>%
  mutate(priming_predictor = "absolute_context_initial_difference")

h6_prime_like_rank <- bind_rows(
  h6_prime_context_target_rank,
  h6_prime_relative_rank,
  h6_prime_abs_relative_rank
) %>%
  group_by(hypothesis, priming_predictor) %>%
  mutate(p_fdr_within_predictor = p.adjust(p.value, method = "BH")) %>%
  ungroup() %>%
  arrange(hypothesis, priming_predictor, desc(abs_estimate))

h6_prime_like_best <- h6_prime_like_rank %>%
  filter(!is.na(estimate)) %>%
  group_by(hypothesis, priming_predictor) %>%
  slice_max(order_by = abs_estimate, n = 1, with_ties = FALSE) %>%
  ungroup()

apa_table(
  h6_prime_like_best %>%
    transmute(
      Hypothesis = hypothesis,
      `Priming-like predictor` = priming_predictor,
      Metric = metric_short,
      Family = metric_family,
      Object = coalesce(basis_object, object, state_type, ""),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(p_fdr_within_predictor)
    ),
  caption = "Table H6e-2. Best context/prime-like predictors of diagnostic responses."
)
Table H6e-2. Best context/prime-like predictors of diagnostic responses.
Hypothesis Priming-like predictor Metric Family Object n Spearman rho p q
H6_diagnosticity_choice_full absolute_context_initial_difference a in b trace normalized Operator/density geometry rho_global_density 5 1.00 < .001 < .001
H6_diagnosticity_choice_full context_minus_initial_similarity containment a to b Subspace/projector geometry S_lowdin 5 1.00 < .001 < .001
H6_diagnosticity_choice_full context_target_similarity trace inner Operator/density geometry projector_mixture_freq 5 1.00 < .001 < .001
H6_diagnosticity_rating absolute_context_initial_difference b in a trace normalized Operator/density geometry P_concat_direct 44 −.41 .006 .809
H6_diagnosticity_rating context_minus_initial_similarity containment a to b Subspace/projector geometry S_lowdin 44 .40 .007 .853
H6_diagnosticity_rating context_target_similarity containment a to b Subspace/projector geometry S_lowdin 44 .56 < .001 .008
Show code
write_table(h6_prime_like_rank, "h6e_prime_like_context_target_rankings.csv")
write_table(h6_prime_like_best, "h6e_prime_like_best_by_predictor.csv")
Show code
if (nrow(h6_prime_like_best) > 0) {
  h6e_plot <- h6_prime_like_best %>%
    mutate(
      label = paste0(str_replace(hypothesis, "H6_diagnosticity_", ""), "\n", priming_predictor),
      label = fct_reorder(label, abs_estimate)
    ) %>%
    ggplot(aes(x = label, y = estimate, fill = metric_family)) +
    geom_hline(yintercept = 0, linewidth = .35) +
    geom_col(width = .70) +
    coord_flip() +
    scale_y_continuous(limits = c(-1, 1), breaks = seq(-1, 1, .25)) +
    labs(
      title = "Diagnostic context as a priming-like predictor",
      subtitle = "Best metric for each diagnostic outcome and context/initial contrast.",
      x = NULL,
      y = "Best Spearman rho"
    ) +
    theme_apa7()

  h6e_plot
  save_plot(h6e_plot, "h6e_diagnostic_context_as_prime_v10.png", width = 8.5, height = 6)
}
Show code
h6e_best_context <- h6_prime_like_best %>%
  filter(priming_predictor == "context_target_similarity") %>%
  arrange(desc(abs_estimate)) %>%
  slice_head(n = 1)

h6e_best_relative <- h6_prime_like_best %>%
  filter(priming_predictor == "context_minus_initial_similarity") %>%
  arrange(desc(abs_estimate)) %>%
  slice_head(n = 1)

context_sentence <- if (nrow(h6e_best_context) > 0) {
  paste0(
    "The strongest pure context-target predictor is ", h6e_best_context$metric_short[[1]], " from ",
    h6e_best_context$metric_family[[1]], " (rho = ", apa_r(h6e_best_context$estimate[[1]]), "). "
  )
} else {
  "No context-target predictor was estimable from the available pairwise metric table. "
}

relative_sentence <- if (nrow(h6e_best_relative) > 0) {
  paste0(
    "The strongest context-minus-initial predictor is ", h6e_best_relative$metric_short[[1]], " from ",
    h6e_best_relative$metric_family[[1]], " (rho = ", apa_r(h6e_best_relative$estimate[[1]]), "). "
  )
} else {
  "No context-minus-initial predictor was estimable from the available pairwise metric table. "
}

cat(
  "**Interpretation.** No separate priming task is present in the experimental instrument set, but the diagnostic context plays a priming-like role: it is the element that should shift which features are active or diagnostic in the comparison. ",
  context_sentence,
  relative_sentence,
  "If context-target similarity is predictive, the context behaves like an attractor or prime. If the context-minus-initial contrast is stronger, the effect is more specifically diagnostic: the context changes the relative usefulness of target features rather than merely increasing relatedness in general.\n"
)

Interpretation. No separate priming task is present in the experimental instrument set, but the diagnostic context plays a priming-like role: it is the element that should shift which features are active or diagnostic in the comparison. The strongest pure context-target predictor is trace inner from Operator/density geometry (rho = 1.00). The strongest context-minus-initial predictor is containment a to b from Subspace/projector geometry (rho = 1.00). If context-target similarity is predictive, the context behaves like an attractor or prime. If the context-minus-initial contrast is stronger, the effect is more specifically diagnostic: the context changes the relative usefulness of target features rather than merely increasing relatedness in general.

15 Representation alignment and variant sensitivity

This section is the methodological conscience of the notebook. The pipeline contains arbitrary decisions: clustering model, dimensionality rule, global versus local representation, projector versus density normalization. Alignment and sensitivity analyses ask whether the conclusions depend on those decisions. A result is more persuasive when the relevant family remains stable across variants, or when the best-performing variant has a clear theoretical reason for performing well.

These analyses are not direct hypothesis tests. They assess whether the representation choices are stable. If a metric works only because one arbitrary variant had a good day, that is less convincing than a metric family that remains competitive across clustering and dimensionality decisions.

Show code
apa_table(
  alignment_summary %>%
    transmute(
      `Object A` = object_a,
      `Object B` = object_b,
      n,
      `Mean HS cosine` = apa_num(mean_hs_cosine, 3),
      `SD HS cosine` = apa_num(sd_hs_cosine, 3),
      `Mean trace overlap` = apa_num(mean_trace_overlap, 3)
    ),
  caption = "Table 25. Representation alignment across objects within variants."
)
Table 25. Representation alignment across objects within variants.
Object A Object B n Mean HS cosine SD HS cosine Mean trace overlap
rho_freq_density rho_fit_global_density 952 0.992 0.019 0.020
projector_mixture_freq projector_mixture_fit_global 952 0.988 0.021 0.745
rho_global_density rho_fit_global_density 952 0.879 0.126 0.017
rho_global_density rho_freq_density 952 0.873 0.130 0.017
P_global projector_mixture_freq 952 0.870 0.133 0.750
P_global projector_mixture_fit_global 952 0.869 0.136 0.763
P_global P_concat_direct 952 0.755 0.244 0.755
P_global P_lowdin 952 0.755 0.244 0.755
Show code
apa_table(
  variant_sensitivity_summary %>%
    transmute(
      Object = object,
      `Comparison type` = comparison_type,
      n,
      `Mean HS cosine` = apa_num(mean_hs_cosine, 3),
      `SD HS cosine` = apa_num(sd_hs_cosine, 3),
      `Mean Frobenius distance` = apa_num(mean_frobenius_distance, 3)
    ),
  caption = "Table 26. Variant sensitivity by representation object."
)
Table 26. Variant sensitivity by representation object.
Object Comparison type n Mean HS cosine SD HS cosine Mean Frobenius distance
P_concat_direct Both clustering and dimensionality 1428 0.519 0.141 12.332
P_concat_direct Clustering only 476 0.589 0.182 11.186
P_concat_direct Dimensionality only 1428 0.784 0.201 5.393
P_global Both clustering and dimensionality 1440 0.755 0.176 5.254
P_global Clustering only 480 1.000 0.000 0.000
P_global Dimensionality only 1440 0.755 0.176 5.254
P_lowdin Both clustering and dimensionality 1428 0.519 0.141 12.332
P_lowdin Clustering only 476 0.589 0.182 11.186
P_lowdin Dimensionality only 1428 0.784 0.201 5.393
projector_mixture_fit_global Both clustering and dimensionality 1428 0.685 0.135 6.036
projector_mixture_fit_global Clustering only 476 0.795 0.098 4.602
projector_mixture_fit_global Dimensionality only 1428 0.753 0.174 4.646
projector_mixture_freq Both clustering and dimensionality 1428 0.690 0.134 6.268
projector_mixture_freq Clustering only 476 0.799 0.090 4.816
projector_mixture_freq Dimensionality only 1428 0.760 0.174 4.810
rho_fit_global_density Both clustering and dimensionality 1428 0.706 0.129 0.112
rho_fit_global_density Clustering only 476 0.814 0.084 0.081
rho_fit_global_density Dimensionality only 1428 0.766 0.169 0.094
rho_freq_density Both clustering and dimensionality 1428 0.696 0.133 0.115
rho_freq_density Clustering only 476 0.803 0.090 0.084
rho_freq_density Dimensionality only 1428 0.761 0.172 0.096
rho_global_density Both clustering and dimensionality 1440 0.755 0.176 0.105
rho_global_density Clustering only 480 1.000 0.000 0.000
rho_global_density Dimensionality only 1440 0.755 0.176 0.105
Show code
alignment_plot <- alignment_summary %>%
  mutate(pair = fct_reorder(paste(object_a, object_b, sep = " vs "), mean_hs_cosine)) %>%
  ggplot(aes(x = pair, y = mean_hs_cosine)) +
  geom_col(width = .72) +
  coord_flip() +
  scale_y_continuous(limits = c(0, 1), breaks = seq(0, 1, .1)) +
  labs(
    title = "Representation alignment",
    subtitle = "Higher Hilbert-Schmidt cosine means two representations encode similar structure.",
    x = NULL,
    y = "Mean HS cosine"
  ) +
  theme_apa7()

alignment_plot

Show code
save_plot(alignment_plot, "representation_alignment_summary_v7.png", width = 8.5, height = 5.5)

variant_plot <- variant_sensitivity_summary %>%
  filter(comparison_type != "No detected change") %>%
  mutate(object = fct_reorder(object, mean_hs_cosine)) %>%
  ggplot(aes(x = object, y = mean_hs_cosine, fill = comparison_type)) +
  geom_col(position = position_dodge(width = .72), width = .62) +
  coord_flip() +
  scale_y_continuous(limits = c(0, 1), breaks = seq(0, 1, .1)) +
  scale_fill_manual(values = report_palette) +
  labs(
    title = "Variant sensitivity",
    subtitle = "Lower values indicate stronger dependence on clustering or dimensionality choices.",
    x = NULL,
    y = "Mean HS cosine"
  ) +
  theme_apa7()

variant_plot

Show code
save_plot(variant_plot, "variant_sensitivity_summary_v7.png", width = 8.5, height = 5.5)

16 Cross-hypothesis comparison

The cross-hypothesis map should not be read as a winner-takes-all table. It is a diagnostic map of which formal family is useful for which psychological operation. A good theory need not use the same metric everywhere. In fact, the interesting pattern is that different tasks appear to recruit different levels of representation: centroid vectors for explicit similarity, containment for asymmetry and salience, Bures/fidelity-like geometry for triplets, and contextualized or residual-state mechanisms for diagnosticity.

Show code
family_heatmap_data <- bind_rows(
  h1_rank_all %>% mutate(analysis_block = "H1 asymmetry"),
  h2_rank_all %>% mutate(analysis_block = "H2 salience"),
  h3_rating_rank %>% mutate(analysis_block = "H3 similarity"),
  h3_order_rank %>% mutate(analysis_block = "H3 order"),
  h6_static_rank %>% mutate(analysis_block = str_replace(hypothesis, "H6_diagnosticity_", "H6 static "))
) %>%
  mutate(metric_family = metric_family_from_source(metric_source)) %>%
  group_by(analysis_block, metric_family) %>%
  slice_max(order_by = abs_estimate, n = 1, with_ties = FALSE) %>%
  ungroup()

family_heatmap <- family_heatmap_data %>%
  ggplot(aes(x = metric_family, y = analysis_block, fill = estimate)) +
  geom_tile(color = "white", linewidth = .5) +
  geom_text(aes(label = apa_r(estimate)), size = 3, family = "serif") +
  scale_fill_gradient2(low = "#2166AC", mid = "white", high = "#B2182B", midpoint = 0, limits = c(-1, 1)) +
  labs(
    title = "Best association by metric family and analysis block",
    subtitle = "Cells show the best Spearman rho within each family. This is a map, not a multiple-comparison victory parade.",
    x = NULL,
    y = NULL,
    fill = "rho"
  ) +
  theme_apa7() +
  theme(axis.text.x = element_text(angle = 35, hjust = 1))

family_heatmap

Show code
save_plot(family_heatmap, "cross_hypothesis_metric_family_heatmap_v7.png", width = 9.5, height = 5.8)
Show code
apa_table(
  best_predictor_by_block %>%
    transmute(
      `Analysis block` = display_block,
      Family = metric_family,
      Metric = display_metric,
      Representation = coalesce(basis_object, object, density_object, state_type, state_vector_source, ""),
      Variant = str_trunc(coalesce(variant_label, variant_short(variant), ""), 55),
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      q = apa_p(coalesce(p_fdr_within_hypothesis, p_fdr))
    ),
  caption = "Table 27. Cross-hypothesis best predictors."
)
Table 27. Cross-hypothesis best predictors.
Analysis block Family Metric Representation Variant n Spearman rho p q
H1 ipsative asymmetry Subspace/projector geometry Containment asymmetry S_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 30 .90 < .001 < .001
H2 salience Subspace/projector geometry Containment asymmetry S_lowdin bayesian_gmm | g=gavish_donoho | l=gavish_donoho 30 −.90 < .001 < .001
H2b Likert salience Corpus frequency baseline log10 n occurrences concept-level representation 44 .89 < .001 < .001
H3 Likert similarity Classical vector baseline Angular distance, reversed contour_centroid classical 89 .51 < .001 < .001
H3 order asymmetry Operator/density geometry Containment asymmetry P_concat_direct bayesian_gmm | g=gavish_donoho | l=gavish_donoho 89 .32 .002 .029
H4 additive triangle Triplet distance geometry Bures distance, reversed S_global bayesian_gmm | g=horn_wishart | l=horn_wishart 66 .49 < .001 < .001
H4 multiplicative triangle Triplet similarity geometry Density fidelity S_global bayesian_gmm | g=profile_likelihood | l=profile_like... 66 .55 < .001 < .001
H6 static H6_diagnosticity_choice_full Operator/density geometry frobenius distance rho_fit_global_density gmm_bic | g=profile_likelihood | l=profile_likelihood 66 .40 < .001 .235
H6 static H6_diagnosticity_rating Operator/density geometry Containment asymmetry rho_freq_density gmm_bic | g=gavish_donoho | l=gavish_donoho 44 −.69 < .001 < .001
H6 contextualized H6_diagnosticity_choice_full Lüders contextualized density Contextual density HS inner S_global bayesian_gmm | g=profile_likelihood | l=profile_like... 212 .36 < .001 < .001
H6 perplexity H6_diagnosticity_choice_full Perplexity-inhibited diagnostic perplexity residual projector to target containment S_global bayesian_gmm | g=horn_wishart | l=horn_wishart 212 −.26 < .001 .007

17 Data-first check: observed conditions and response times

The previous sections use the thesis and the human-aligned Phase 4 designs as the main organizational frame. That is appropriate for the paper logic, but it is not enough for the exploratory analyses. Some variables were collected but not fully developed in the thesis document, especially response times and the explicit contextuality/priming markers (mediator, pseudoword, and congruent_priming). For those variables, the empirical data files are the authoritative source. Methodology, for once, does not have to pretend the appendix remembered everything.

This section therefore performs a data-driven audit of the observed experimental structure. It asks: (a) which extra conditions were actually collected, (b) which tasks have usable response times, and (c) whether those conditions should moderate the main semantic contrasts.

Show code
# A slightly more readable plotting theme for the data-driven extension.
# The previous report keeps APA-like tables; these figures prioritize clarity.
theme_report <- function(base_size = 12) {
  ggplot2::theme_minimal(base_size = base_size) +
    theme(
      panel.grid.minor = element_blank(),
      panel.grid.major.y = element_blank(),
      plot.title = element_text(face = "bold", size = base_size + 2, margin = margin(b = 5)),
      plot.subtitle = element_text(size = base_size, margin = margin(b = 8)),
      axis.title = element_text(size = base_size),
      axis.text = element_text(size = base_size - 1),
      legend.position = "bottom",
      legend.title = element_blank(),
      strip.text = element_text(face = "bold"),
      plot.margin = margin(8, 16, 8, 8)
    )
}
Show code
phase2_tables_dir <- if (dir.exists(file.path(phase2_dir, "tables"))) file.path(phase2_dir, "tables") else phase2_dir

phase2_observed_files <- list(
  responses_all = file.path(phase2_tables_dir, "responses_all_long.csv"),
  contextuality_aggregate = file.path(phase2_tables_dir, "responses_contextuality_aggregate_main.csv"),
  contextuality_raw = file.path(phase2_tables_dir, "responses_contextuality_raw_long.csv"),
  rt_summary = file.path(phase2_tables_dir, "rt_summary_phase2.csv"),
  items_catalog = file.path(phase2_tables_dir, "items_catalog_phase2.csv"),
  data_dictionary = file.path(phase2_tables_dir, "data_dictionary_phase2.csv")
)

responses_all_observed <- read_csv_fast(phase2_observed_files$responses_all, required = FALSE)
rt_summary_observed <- read_csv_fast(phase2_observed_files$rt_summary, required = FALSE)
items_catalog_observed <- read_csv_fast(phase2_observed_files$items_catalog, required = FALSE)
data_dictionary_observed <- read_csv_fast(phase2_observed_files$data_dictionary, required = FALSE)

if (nrow(responses_all_observed) > 0) {
  responses_all_observed <- responses_all_observed %>%
    mutate(
      rt_ms = as.numeric(rt_ms),
      log_rt = as.numeric(log_rt),
      rating = as.numeric(rating),
      mediator = as.numeric(mediator),
      pseudoword = as.numeric(pseudoword),
      congruent_priming = as.numeric(congruent_priming),
      rt_clean = !is.na(rt_ms) & rt_ms >= 300 & rt_ms <= 30000,
      response_binary_d = case_when(
        as.character(response_coded) == "d" ~ 1,
        as.character(response_coded) == "a" ~ 0,
        TRUE ~ NA_real_
      ),
      selected_first = case_when(
        as.character(response_coded) == "a" ~ 1,
        as.character(response_coded) == "d" ~ 0,
        TRUE ~ NA_real_
      )
    )
}

17.1 Observed condition structure

The observed data show that the contextuality task carries three condition variables that are not merely decorative metadata:

  • mediator: whether the context is semantically mediated by the target concept.
  • pseudoword: whether the context is an invented/novel form rather than a lexicalized term.
  • congruent_priming: whether the prime/context is congruent with the expected conceptual activation.

This means that the “priming” issue should not be inferred only from the diagnosticity block. In the actual data, priming-like information is explicitly available in the contextuality task.

Show code
observed_condition_audit <- responses_all_observed %>%
  summarise(
    total_rows = n(),
    rows_with_rt = sum(!is.na(rt_ms)),
    rows_with_clean_rt = sum(rt_clean, na.rm = TRUE),
    rows_with_mediator = sum(!is.na(mediator)),
    rows_with_pseudoword = sum(!is.na(pseudoword)),
    rows_with_congruent_priming = sum(!is.na(congruent_priming)),
    contextuality_rows = sum(experiment == "contextuality", na.rm = TRUE),
    diagnosticity_rows = sum(experiment == "diagnosticity", na.rm = TRUE),
    saliency_rows = sum(experiment == "saliency", na.rm = TRUE),
    asymmetry_rows = sum(experiment == "asymmetry", na.rm = TRUE)
  ) %>%
  pivot_longer(everything(), names_to = "quantity", values_to = "value")

apa_table(
  observed_condition_audit,
  caption = "Table 28. Data-driven audit of observed extra variables in the tidy human data."
)
Table 28. Data-driven audit of observed extra variables in the tidy human data.
quantity value
total_rows 101119
rows_with_rt 77110
rows_with_clean_rt 76694
rows_with_mediator 5180
rows_with_pseudoword 5180
rows_with_congruent_priming 3332
contextuality_rows 5180
diagnosticity_rows 11429
saliency_rows 27380
asymmetry_rows 44550
Show code
write_table(observed_condition_audit, "table_28_observed_condition_audit.csv")
Show code
contextuality_condition_summary <- responses_all_observed %>%
  filter(experiment == "contextuality") %>%
  mutate(
    mediator = factor(mediator, levels = c(0, 1), labels = c("No mediator", "Mediator")),
    pseudoword = factor(pseudoword, levels = c(0, 1), labels = c("Lexical context", "Pseudoword")),
    congruent_priming = factor(congruent_priming, levels = c(0, 1), labels = c("Incongruent/other", "Congruent prime"))
  ) %>%
  group_by(mediator, pseudoword, congruent_priming) %>%
  summarise(
    n = n(),
    n_clean_rt = sum(rt_clean, na.rm = TRUE),
    prop_d_response = mean(response_binary_d, na.rm = TRUE),
    median_rt = median(rt_ms[rt_clean], na.rm = TRUE),
    mean_log_rt = mean(log_rt[rt_clean], na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(mediator, pseudoword, congruent_priming)

apa_table(
  contextuality_condition_summary %>%
    mutate(
      prop_d_response = apa_num(prop_d_response, 3),
      median_rt = apa_num(median_rt, 1),
      mean_log_rt = apa_num(mean_log_rt, 3)
    ),
  caption = "Table 29. Observed contextuality/priming condition structure and response times."
)
Table 29. Observed contextuality/priming condition structure and response times.
mediator pseudoword congruent_priming n n_clean_rt prop_d_response median_rt mean_log_rt
No mediator Lexical context NA 1278 1278 0.667 1627.2 7.427
No mediator Pseudoword NA 570 569 0.546 1693.4 7.480
Mediator Lexical context Incongruent/other 1272 1271 0.645 2013.0 7.631
Mediator Lexical context Congruent prime 1029 1027 0.661 2063.2 7.639
Mediator Pseudoword Incongruent/other 1031 1031 0.558 2066.3 7.657
Show code
write_table(contextuality_condition_summary, "table_29_observed_contextuality_condition_summary.csv")
Show code
contextuality_condition_plot <- contextuality_condition_summary %>%
  mutate(
    condition_label = paste(mediator, pseudoword, congruent_priming, sep = "\n"),
    condition_label = fct_reorder(condition_label, median_rt)
  ) %>%
  ggplot(aes(x = condition_label, y = median_rt, fill = mediator)) +
  geom_col(width = .72, alpha = .88) +
  geom_text(aes(label = round(median_rt)), hjust = -0.10, size = 3.2) +
  coord_flip() +
  scale_fill_manual(values = c("#6BAED6", "#2171B5")) +
  scale_y_continuous(expand = expansion(mult = c(0, .12))) +
  labs(
    title = "Observed contextuality/priming conditions differ in response time",
    subtitle = "Median clean RT by mediator, pseudoword, and congruent-priming markers.",
    x = NULL,
    y = "Median RT (ms)"
  ) +
  theme_report()

contextuality_condition_plot

Show code
save_plot(contextuality_condition_plot, "observed_contextuality_condition_rt_v11.png", width = 9, height = 5.2)

17.2 Response-time availability and task-level interpretation

Response times are available for asymmetry, salience, and contextuality, but not uniformly for all blocks. This matters because RTs offer a different dependent variable: not what people chose, but how hard it was for them to decide. In this framework, RT is not an afterthought. If a concept pair has a clear inclusion relation, high salience contrast, or strong contextual activation, the response should often be faster. Conversely, ambiguous pairs, weak contrasts, or conflicting contexts should produce slower responses.

Show code
rt_task_summary_observed <- responses_all_observed %>%
  group_by(experiment, subexperiment, response_type) %>%
  summarise(
    n_rows = n(),
    n_rt = sum(!is.na(rt_ms)),
    n_clean_rt = sum(rt_clean, na.rm = TRUE),
    median_rt = median(rt_ms[rt_clean], na.rm = TRUE),
    mean_rt = mean(rt_ms[rt_clean], na.rm = TRUE),
    sd_rt = sd(rt_ms[rt_clean], na.rm = TRUE),
    pct_rt_lt_300 = mean(rt_ms < 300, na.rm = TRUE),
    pct_rt_gt_30000 = mean(rt_ms > 30000, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(
    pct_rt_lt_300 = 100 * pct_rt_lt_300,
    pct_rt_gt_30000 = 100 * pct_rt_gt_30000
  )

apa_table(
  rt_task_summary_observed %>%
    mutate(
      median_rt = apa_num(median_rt, 1),
      mean_rt = apa_num(mean_rt, 1),
      sd_rt = apa_num(sd_rt, 1),
      pct_rt_lt_300 = apa_num(pct_rt_lt_300, 2),
      pct_rt_gt_30000 = apa_num(pct_rt_gt_30000, 2)
    ),
  caption = "Table 30. Response-time availability and task-level RT summaries."
)
Table 30. Response-time availability and task-level RT summaries.
experiment subexperiment response_type n_rows n_rt n_clean_rt median_rt mean_rt sd_rt pct_rt_lt_300 pct_rt_gt_30000
asymmetry asymmetry_1_forced_choice forced_choice_ad 11250 11250 11239 2930.1 3634.9 2530.2 0.01 0.09
asymmetry asymmetry_2_a_direct rating_1_9 17370 17370 17164 3146.8 3686.7 2131.6 1.13 0.06
asymmetry asymmetry_2_b_reverse rating_1_9 15930 15930 15740 2977.4 3462.7 1947.8 1.13 0.06
contextuality contextuality_aggregate_main binary_a_d_modal_aggregate 5180 5180 5176 1887.7 2397.1 1931.8 0.02 0.06
diagnosticity diagnosticity_1_choice_cleaned choice_country_code 4059 0 0
diagnosticity diagnosticity_2_rating_cleaned rating_0_6 7370 0 0
gk gk_test binary_correct 12580 0 0
saliency saliency_1_forced_choice forced_choice_ad 11100 11100 11098 1566.3 1965.2 1390.2 0.00 0.02
saliency saliency_2_single_rating rating_1_9 16280 16280 16277 1924.6 2344.9 1614.6 0.01 0.01
Show code
write_table(rt_task_summary_observed, "table_30_observed_rt_summary_by_task.csv")
Show code
rt_task_plot <- rt_task_summary_observed %>%
  filter(n_clean_rt > 0) %>%
  mutate(
    task_label = str_replace_all(subexperiment, "_", " "),
    task_label = fct_reorder(task_label, median_rt)
  ) %>%
  ggplot(aes(x = task_label, y = median_rt, fill = experiment)) +
  geom_col(width = .70, alpha = .88) +
  geom_text(aes(label = round(median_rt)), hjust = -0.12, size = 3.2) +
  coord_flip() +
  scale_fill_manual(values = c(
    "asymmetry" = "#756BB1",
    "saliency" = "#31A354",
    "contextuality" = "#3182BD",
    "diagnosticity" = "#E6550D",
    "gk" = "#636363"
  )) +
  scale_y_continuous(expand = expansion(mult = c(0, .15))) +
  labs(
    title = "Median response time by task",
    subtitle = "Only clean RTs between 300 ms and 30,000 ms are summarized.",
    x = NULL,
    y = "Median RT (ms)"
  ) +
  theme_report()

rt_task_plot

Show code
save_plot(rt_task_plot, "observed_rt_by_task_v11.png", width = 9, height = 5)

17.3 Forced-choice RT as decision clarity

For forced-choice asymmetry and salience, the simplest RT hypothesis is a clarity hypothesis: items with a stronger human preference should be answered faster. This does not require adding a new conceptual mechanism. It follows from the same logic as containment and salience contrast: a strong directional relation should reduce competition between alternatives.

Show code
forced_choice_rt_item_summary <- responses_all_observed %>%
  filter(
    subexperiment %in% c("asymmetry_1_forced_choice", "saliency_1_forced_choice"),
    rt_clean,
    !is.na(selected_first)
  ) %>%
  group_by(experiment, subexperiment, item_label_original, stimulus_1_code, stimulus_2_code) %>%
  summarise(
    n = n(),
    prop_choose_first = mean(selected_first, na.rm = TRUE),
    choice_clarity = abs(prop_choose_first - .5),
    median_rt = median(rt_ms, na.rm = TRUE),
    mean_log_rt = mean(log_rt, na.rm = TRUE),
    .groups = "drop"
  )

forced_choice_rt_clarity_tests <- forced_choice_rt_item_summary %>%
  group_by(experiment, subexperiment) %>%
  group_modify(~safe_cor(.x, x = "choice_clarity", y = "median_rt", method = "spearman")) %>%
  ungroup() %>%
  mutate(
    interpretation = case_when(
      estimate < 0 ~ "Clearer forced choices are faster",
      estimate > 0 ~ "Clearer forced choices are slower",
      TRUE ~ "No monotonic RT-clarity trend"
    )
  )

apa_table(
  forced_choice_rt_clarity_tests %>%
    transmute(
      Experiment = experiment,
      Task = subexperiment,
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value),
      Interpretation = interpretation
    ),
  caption = "Table 31. Forced-choice response times as a function of item-level decision clarity."
)
Table 31. Forced-choice response times as a function of item-level decision clarity.
Experiment Task n Spearman rho p Interpretation
asymmetry asymmetry_1_forced_choice 30 −.69 < .001 Clearer forced choices are faster
saliency saliency_1_forced_choice 30 −.86 < .001 Clearer forced choices are faster
Show code
write_table(forced_choice_rt_clarity_tests, "table_31_forced_choice_rt_decision_clarity.csv")
Show code
forced_choice_rt_plot <- forced_choice_rt_item_summary %>%
  mutate(task = recode(experiment, asymmetry = "Ipsative asymmetry", saliency = "Ipsative salience")) %>%
  ggplot(aes(x = choice_clarity, y = median_rt)) +
  geom_point(aes(fill = task), shape = 21, size = 2.8, alpha = .78, color = "white") +
  geom_smooth(method = "lm", se = TRUE, linewidth = .8, color = "#252525") +
  facet_wrap(~task, scales = "free_y") +
  scale_fill_manual(values = c("Ipsative asymmetry" = "#756BB1", "Ipsative salience" = "#31A354")) +
  labs(
    title = "Forced-choice RT tracks decision clarity",
    subtitle = "Choice clarity is |P(choose first) − .50| at the item level.",
    x = "Item-level choice clarity",
    y = "Median RT (ms)"
  ) +
  theme_report()

forced_choice_rt_plot

Show code
save_plot(forced_choice_rt_plot, "forced_choice_rt_decision_clarity_v11.png", width = 9, height = 4.8)

17.4 Single-concept salience RT and dimensionality

For Likert salience, RT can be interpreted differently. A highly salient concept may be rated quickly because it is familiar and richly represented, but a concept with many semantic dimensions may also require more integration. These two interpretations compete: frequency/familiarity predicts faster RT; semantic richness may predict either faster access or slower deliberation depending on how the rating task is solved.

Show code
single_salience_rt_summary <- responses_all_observed %>%
  filter(subexperiment == "saliency_2_single_rating", rt_clean, !is.na(rating)) %>%
  group_by(target = stimulus_1_code) %>%
  summarise(
    n = n(),
    mean_salience_rating = mean(rating, na.rm = TRUE),
    median_salience_rating = median(rating, na.rm = TRUE),
    median_rt = median(rt_ms, na.rm = TRUE),
    mean_log_rt = mean(log_rt, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  filter(
    is.finite(mean_salience_rating),
    is.finite(median_rt)
  )

single_salience_rt_tests <- tibble(
  predictor = c("mean_salience_rating", "median_salience_rating"),
  outcome = "median_rt"
) %>%
  rowwise() %>%
  mutate(test = list(safe_cor(single_salience_rt_summary, x = predictor, y = outcome, method = "spearman"))) %>%
  unnest(test) %>%
  ungroup()

fit_linear_quadratic <- function(data, predictor, outcome = "median_rt") {
  d <- data %>%
    filter(!is.na(.data[[predictor]]), !is.na(.data[[outcome]])) %>%
    filter(is.finite(.data[[predictor]]), is.finite(.data[[outcome]]))

  if (nrow(d) < 5 || n_distinct(d[[predictor]]) < 3) {
    return(list(
      comparison = tibble(
        predictor = predictor,
        outcome = outcome,
        model = c("Linear", "Quadratic"),
        n = nrow(d),
        r_squared = NA_real_,
        adj_r_squared = NA_real_,
        AIC = NA_real_,
        BIC = NA_real_,
        delta_r2_vs_linear = c(NA_real_, NA_real_),
        quadratic_increment_p = c(NA_real_, NA_real_)
      ),
      coefficients = tibble(
        predictor = predictor,
        outcome = outcome,
        model = c("Linear", "Quadratic"),
        term = "model_not_estimable",
        estimate = NA_real_,
        std.error = NA_real_,
        statistic = NA_real_,
        p.value = NA_real_
      )
    ))
  }

  linear_formula <- reformulate(predictor, response = outcome)
  quadratic_formula <- as.formula(paste0(outcome, " ~ ", predictor, " + I(", predictor, "^2)"))

  linear_fit <- lm(linear_formula, data = d)
  quadratic_fit <- lm(quadratic_formula, data = d)

  linear_glance <- broom::glance(linear_fit)
  quadratic_glance <- broom::glance(quadratic_fit)
  nested_comparison <- anova(linear_fit, quadratic_fit)

  comparison <- bind_rows(
    tibble(
      predictor = predictor,
      outcome = outcome,
      model = "Linear",
      n = stats::nobs(linear_fit),
      r_squared = linear_glance$r.squared,
      adj_r_squared = linear_glance$adj.r.squared,
      AIC = AIC(linear_fit),
      BIC = BIC(linear_fit),
      delta_r2_vs_linear = NA_real_,
      quadratic_increment_p = NA_real_
    ),
    tibble(
      predictor = predictor,
      outcome = outcome,
      model = "Quadratic",
      n = stats::nobs(quadratic_fit),
      r_squared = quadratic_glance$r.squared,
      adj_r_squared = quadratic_glance$adj.r.squared,
      AIC = AIC(quadratic_fit),
      BIC = BIC(quadratic_fit),
      delta_r2_vs_linear = quadratic_glance$r.squared - linear_glance$r.squared,
      quadratic_increment_p = nested_comparison$`Pr(>F)`[[2]]
    )
  )

  coefficients <- bind_rows(
    broom::tidy(linear_fit) %>% mutate(model = "Linear"),
    broom::tidy(quadratic_fit) %>% mutate(model = "Quadratic")
  ) %>%
    mutate(predictor = predictor, outcome = outcome) %>%
    select(predictor, outcome, model, term, estimate, std.error, statistic, p.value)

  list(comparison = comparison, coefficients = coefficients)
}

single_salience_rt_model_objects <- list(
  mean_salience_rating = fit_linear_quadratic(single_salience_rt_summary, "mean_salience_rating", "median_rt"),
  median_salience_rating = fit_linear_quadratic(single_salience_rt_summary, "median_salience_rating", "median_rt")
)

single_salience_rt_model_comparison <- bind_rows(lapply(single_salience_rt_model_objects, `[[`, "comparison"))
single_salience_rt_model_coefficients <- bind_rows(lapply(single_salience_rt_model_objects, `[[`, "coefficients"))

apa_table(
  single_salience_rt_tests %>%
    transmute(
      Predictor = predictor,
      Outcome = outcome,
      n,
      `Spearman rho` = apa_r(estimate),
      p = apa_p(p.value)
    ),
  caption = "Table 32. Single-concept salience ratings and response times."
)
Table 32. Single-concept salience ratings and response times.
Predictor Outcome n Spearman rho p
mean_salience_rating median_rt 44 .36 .016
median_salience_rating median_rt 44 .33 .030
Show code
write_table(single_salience_rt_tests, "table_32_single_salience_rt_tests.csv")

apa_table(
  single_salience_rt_model_comparison %>%
    transmute(
      Predictor = predictor,
      Outcome = outcome,
      Model = model,
      n,
      `` = apa_num(r_squared, 3),
      `Adjusted R²` = apa_num(adj_r_squared, 3),
      AIC = apa_num(AIC, 2),
      BIC = apa_num(BIC, 2),
      `ΔR² vs linear` = apa_num(delta_r2_vs_linear, 3),
      `Quadratic increment p` = apa_p(quadratic_increment_p)
    ),
  caption = "Table 32b. Linear versus quadratic models for single-concept salience and response time."
)
Table 32b. Linear versus quadratic models for single-concept salience and response time.
Predictor Outcome Model n Adjusted R² AIC BIC ΔR² vs linear Quadratic increment p
mean_salience_rating median_rt Linear 44 0.085 0.063 607.83 613.19
mean_salience_rating median_rt Quadratic 44 0.680 0.665 563.55 570.69 0.595 < .001
median_salience_rating median_rt Linear 44 0.081 0.059 608.05 613.40
median_salience_rating median_rt Quadratic 44 0.513 0.489 582.11 589.24 0.432 < .001
Show code
write_table(single_salience_rt_model_comparison, "table_32b_single_salience_rt_linear_quadratic_comparison.csv")

apa_table(
  single_salience_rt_model_coefficients %>%
    transmute(
      Predictor = predictor,
      Model = model,
      Term = term,
      b = apa_num(estimate, 3),
      SE = apa_num(std.error, 3),
      t = apa_num(statistic, 2),
      p = apa_p(p.value)
    ),
  caption = "Table 32c. Coefficients from linear and quadratic RT models."
)
Table 32c. Coefficients from linear and quadratic RT models.
Predictor Model Term b SE t p
mean_salience_rating Linear (Intercept) 1792.407 84.599 21.19 < .001
mean_salience_rating Linear mean_salience_rating 33.394 16.898 1.98 .055
mean_salience_rating Quadratic (Intercept) 814.413 122.816 6.63 < .001
mean_salience_rating Quadratic mean_salience_rating 515.786 56.115 9.19 < .001
mean_salience_rating Quadratic I(mean_salience_rating^2) -48.776 5.581 -8.74 < .001
median_salience_rating Linear (Intercept) 1819.085 74.215 24.51 < .001
median_salience_rating Linear median_salience_rating 27.645 14.404 1.92 .062
median_salience_rating Quadratic (Intercept) 1278.015 105.069 12.16 < .001
median_salience_rating Quadratic median_salience_rating 322.639 50.054 6.45 < .001
median_salience_rating Quadratic I(median_salience_rating^2) -30.130 4.996 -6.03 < .001
Show code
write_table(single_salience_rt_model_coefficients, "table_32c_single_salience_rt_linear_quadratic_coefficients.csv")
Show code
mean_quad_row <- single_salience_rt_model_comparison %>%
  filter(predictor == "mean_salience_rating", model == "Quadratic") %>%
  slice_head(n = 1)

mean_linear_row <- single_salience_rt_model_comparison %>%
  filter(predictor == "mean_salience_rating", model == "Linear") %>%
  slice_head(n = 1)

quad_coef_row <- single_salience_rt_model_coefficients %>%
  filter(predictor == "mean_salience_rating", model == "Quadratic", str_detect(term, "\\^2")) %>%
  slice_head(n = 1)

if (nrow(mean_quad_row) > 0 && nrow(mean_linear_row) > 0) {
  curvature_direction <- case_when(
    nrow(quad_coef_row) == 0 | is.na(quad_coef_row$estimate[[1]]) ~ "unknown curvature",
    quad_coef_row$estimate[[1]] < 0 ~ "an inverted-U pattern",
    quad_coef_row$estimate[[1]] > 0 ~ "a U-shaped pattern",
    TRUE ~ "no visible curvature"
  )

  cat(paste0(
    "**Interpretation.** The linear model tests whether response time changes monotonically with single-concept salience. ",
    "The quadratic model tests whether intermediate salience values are processed differently from very low or very high salience values. ",
    "For the mean salience predictor, the quadratic model changes explained variance by ΔR² = ",
    apa_num(mean_quad_row$delta_r2_vs_linear[[1]], 3),
    " relative to the linear model, with incremental p ",
    apa_p(mean_quad_row$quadratic_increment_p[[1]]),
    ". The estimated curvature suggests ", curvature_direction,
    ". If this quadratic term is reliable, the result would fit a two-process reading: very low-salience concepts may be answered quickly because they are easy to reject as prominent, very high-salience concepts may be answered quickly because they are familiar, and intermediate concepts may require more deliberative integration. A splendidly inconvenient pattern for anyone hoping cognition was linear.\n"
  ))
}

Interpretation. The linear model tests whether response time changes monotonically with single-concept salience. The quadratic model tests whether intermediate salience values are processed differently from very low or very high salience values. For the mean salience predictor, the quadratic model changes explained variance by ΔR² = 0.595 relative to the linear model, with incremental p < .001. The estimated curvature suggests an inverted-U pattern. If this quadratic term is reliable, the result would fit a two-process reading: very low-salience concepts may be answered quickly because they are easy to reject as prominent, very high-salience concepts may be answered quickly because they are familiar, and intermediate concepts may require more deliberative integration. A splendidly inconvenient pattern for anyone hoping cognition was linear.

Show code
single_salience_rt_plot <- single_salience_rt_summary %>%
  ggplot(aes(x = mean_salience_rating, y = median_rt)) +
  geom_point(shape = 21, size = 3.2, alpha = .84, fill = "#31A354", color = "white", stroke = .45) +
  geom_smooth(
    method = "lm",
    formula = y ~ x,
    se = FALSE,
    linewidth = .75,
    linetype = "dashed",
    color = "#595959"
  ) +
  geom_smooth(
    method = "lm",
    formula = y ~ poly(x, 2, raw = TRUE),
    se = TRUE,
    linewidth = 1.05,
    color = "#0072B2",
    fill = "#D9EAF7",
    alpha = .35
  ) +
  labs(
    title = "Single-concept salience and response time",
    subtitle = "Dashed line = simple linear model; blue curve = quadratic model.",
    x = "Mean Likert salience rating",
    y = "Median RT (ms)"
  ) +
  theme_report()

single_salience_rt_plot

Show code
save_plot(single_salience_rt_plot, "single_salience_rating_rt_linear_quadratic_v12.png", width = 8, height = 5.2)

17.5 Data-driven priming/contextuality models

The contextuality block contains an explicit priming-related variable. This gives us a cleaner interpretation than treating diagnosticity as the only context effect. The diagnosticity task is a higher-level contextual choice problem; the contextuality task is the more direct place to evaluate whether congruent primes, mediators, and pseudowords alter binary judgments and response speed.

Show code
contextuality_model_data <- responses_all_observed %>%
  filter(experiment == "contextuality") %>%
  mutate(
    mediator_f = factor(mediator, levels = c(0, 1), labels = c("no_mediator", "mediator")),
    pseudoword_f = factor(pseudoword, levels = c(0, 1), labels = c("lexical", "pseudoword")),
    congruent_priming_f = factor(congruent_priming, levels = c(0, 1), labels = c("not_congruent", "congruent"))
  )

fit_tidy_safe <- function(expr) {
  tryCatch(
    broom::tidy(expr),
    error = function(e) tibble(term = "model_failed", estimate = NA_real_, std.error = NA_real_, statistic = NA_real_, p.value = NA_real_, note = e$message)
  )
}

contextuality_binary_model <- contextuality_model_data %>%
  filter(!is.na(response_binary_d), !is.na(mediator_f), !is.na(pseudoword_f))

contextuality_binary_model_table <- if (nrow(contextuality_binary_model) > 0) {
  fit_tidy_safe(glm(response_binary_d ~ mediator_f * pseudoword_f + mediator_f * congruent_priming_f, data = contextuality_binary_model, family = binomial())) %>%
    mutate(model = "Binary contextuality response")
} else {
  tibble(model = "Binary contextuality response", term = "no_data", estimate = NA_real_, std.error = NA_real_, statistic = NA_real_, p.value = NA_real_)
}

contextuality_rt_model_data <- contextuality_model_data %>%
  filter(rt_clean, !is.na(log_rt), !is.na(mediator_f), !is.na(pseudoword_f))

contextuality_rt_model_table <- if (nrow(contextuality_rt_model_data) > 0) {
  fit_tidy_safe(lm(log_rt ~ mediator_f * pseudoword_f + mediator_f * congruent_priming_f, data = contextuality_rt_model_data)) %>%
    mutate(model = "Log RT contextuality")
} else {
  tibble(model = "Log RT contextuality", term = "no_data", estimate = NA_real_, std.error = NA_real_, statistic = NA_real_, p.value = NA_real_)
}

contextuality_model_tables <- bind_rows(contextuality_binary_model_table, contextuality_rt_model_table)

apa_table(
  contextuality_model_tables %>%
    transmute(
      Model = model,
      Term = term,
      Estimate = apa_num(estimate, 3),
      SE = apa_num(std.error, 3),
      Statistic = apa_num(statistic, 2),
      p = apa_p(p.value)
    ),
  caption = "Table 33. Data-driven contextuality/priming models from observed tidy data."
)
Table 33. Data-driven contextuality/priming models from observed tidy data.
Model Term Estimate SE Statistic p
Binary contextuality response model_failed
Log RT contextuality model_failed
Show code
write_table(contextuality_model_tables, "table_33_contextuality_priming_models.csv")

17.6 Implications for the interpretation of previous sections

The data-driven audit changes how the exploratory part of the report should be read:

  1. Salience/similarity conditions from the annex remain useful as planned item-level design factors, but they should not be the only moderators considered.
  2. Contextuality has explicit priming-related variables (congruent_priming, mediator, pseudoword). These should be analyzed directly rather than reconstructed from the thesis narrative.
  3. RTs are available for asymmetry, salience, and contextuality. RT should therefore be treated as a secondary behavioral outcome: it can test whether computational clarity, salience contrast, or contextual congruence reduces decision difficulty.
  4. Diagnosticity lacks RT in the tidy output, so diagnostic context should be analyzed mainly through choice/rating behavior unless a different raw source is recovered.

For the paper, this suggests a clean separation: Phase 4 main results predict what humans choose or rate; the RT extensions test how difficult those decisions are. If the same computational metric predicts both choice direction and speed, the model has a stronger psychological interpretation. If it predicts choice but not RT, it may still describe the endpoint of judgment without describing processing fluency.

18 Main analytical conclusions

The main conclusion is not that quantum-like metrics universally outperform classical baselines. That would be too convenient, and convenience is usually where theories go to become marketing. The more useful conclusion is differentiated: classical centroids are strong for scalar similarity ratings, while subspace and density-like metrics are much stronger for directional, relational, and context-sensitive phenomena.

Show code
cat(
  "1. **Asymmetry and salience** are best captured by directional quantum-like structure, especially containment asymmetry. The relevant object is not merely a vector, but a relation of inclusion between conceptual subspaces.\n\n",
  "2. **Explicit Likert similarity** is the friendliest domain for classical vector baselines. If centroid cosine performs strongly here, that supports the interpretation that average semantic proximity is enough for scalar similarity ratings.\n\n",
  "3. **Triplet structure** is better evaluated through continuous residuals than through rare binary violations. Bures/fidelity/overlap-type metrics are especially useful because they summarize geometry without reducing everything to a raw cosine.\n\n",
  "4. **Diagnosticity** is mixed by design, because it involves context. Static metrics can predict some rating structure, but contextualized and perplexity-inhibited metrics are the theoretically more relevant families.\n\n",
  "5. **Robustness matters.** If the same family wins across Bayesian GMM and GMM-BIC or across several dimensionality criteria, it deserves more trust. If a result depends on one exact variant, it deserves a smaller font and a suspicious look.\n"
)
  1. Asymmetry and salience are best captured by directional quantum-like structure, especially containment asymmetry. The relevant object is not merely a vector, but a relation of inclusion between conceptual subspaces.

  2. Explicit Likert similarity is the friendliest domain for classical vector baselines. If centroid cosine performs strongly here, that supports the interpretation that average semantic proximity is enough for scalar similarity ratings.

  3. Triplet structure is better evaluated through continuous residuals than through rare binary violations. Bures/fidelity/overlap-type metrics are especially useful because they summarize geometry without reducing everything to a raw cosine.

  4. Diagnosticity is mixed by design, because it involves context. Static metrics can predict some rating structure, but contextualized and perplexity-inhibited metrics are the theoretically more relevant families.

  5. Robustness matters. If the same family wins across Bayesian GMM and GMM-BIC or across several dimensionality criteria, it deserves more trust. If a result depends on one exact variant, it deserves a smaller font and a suspicious look.

19 Interpretation guide for writing the paper

The results should be written as a differentiated account of conceptual similarity. The model does not need to defeat cosine everywhere. Instead, the strongest claim is that different psychological tasks expose different formal levels of conceptual representation:

  • Explicit Likert similarity is well approximated by average semantic proximity. This supports the centroid as a strong classical baseline.
  • Ipsative asymmetry and salience require directional structure. Containment between subspaces is the theoretically central mechanism.
  • Triangular and multiplicative structure is better evaluated with distance/fidelity-like relations between subspace-derived objects than with raw vector similarity.
  • Diagnosticity requires explicit context or competition mechanisms; static pairwise similarity is informative but incomplete.

The arbitrary choices in the pipeline should be reported transparently. The article should emphasize that the analysis intentionally retains several dimensionality and clustering variants to check whether results are metric-family effects rather than isolated parameter accidents.

20 Exported files

All tables and figures are written to:

Show code
paths$output_dir
[1] "/home/alex/Documentos/RESEARCH/QuantumNLP/Paper1_StructuralModel/Computational_data/phase4_R_analysis_outputs_v12"
Show code
list.files(file.path(output_dir, "tables"), pattern = "\\.csv$", full.names = FALSE) %>% head(20)
 [1] "best_predictor_by_analysis_block.csv"                "cosine_vs_quantum_comparisons.csv"                  
 [3] "h1_all_metric_rankings.csv"                          "h1_directional_metric_rankings.csv"                 
 [5] "h2_all_metric_rankings.csv"                          "h2_directional_metric_rankings.csv"                 
 [7] "h2b_likert_salience_by_target.csv"                   "h2b_likert_salience_dimensionality_correlations.csv"
 [9] "h2b_likert_salience_frequency_adjusted_models.csv"   "h2c_annex_condition_audit.csv"                      
[11] "h2c_concept_condition_summary.csv"                   "h2c_concept_condition_tests.csv"                    
[13] "h2c_human_condition_summary.csv"                     "h2c_pairwise_condition_metric_tests.csv"            
[15] "h2c_similarity_condition_lm.csv"                     "h2c_similarity_condition_summary.csv"               
[17] "h2d_best_metric_by_condition.csv"                    "h2d_condition_metric_rankings.csv"                  
[19] "h2d_condition_moderation_models.csv"                 "h3_human_order_asymmetry_test.csv"                  
Show code
list.files(file.path(output_dir, "figures"), pattern = "\\.png$", full.names = FALSE) %>% head(20)
 [1] "cross_hypothesis_metric_family_heatmap_v7.png"               
 [2] "executive_cosine_vs_quantum_comparison.png"                  
 [3] "forced_choice_rt_decision_clarity_v11.png"                   
 [4] "h1_primary_metric_scatter_v7.png"                            
 [5] "h1_top_directional_metric_correlations_v7.png"               
 [6] "h2_primary_metric_scatter_v7.png"                            
 [7] "h2_top_directional_metric_correlations_v7.png"               
 [8] "h2b_likert_salience_best_predictor_scatter_v8.png"           
 [9] "h2b_likert_salience_dimensionality_correlations_v8.png"      
[10] "h2b_likert_salience_frequency_scatter_v8.png"                
[11] "h2c_best_computational_similarity_condition_separator_v9.png"
[12] "h2c_concept_salience_dimensionality_by_planned_label_v9.png" 
[13] "h2c_ipsative_responses_by_salience_condition_v9.png"         
[14] "h3_rating_best_by_metric_family_v7.png"                      
[15] "h3_rating_primary_metric_scatter_v7.png"                     
[16] "h4_additive_primary_residual_scatter_v7.png"                 
[17] "h4_additive_triangle_top_correlations_v7.png"                
[18] "h4_mti_top_correlations_v7.png"                              
[19] "h6_contextualized_best_by_family_v7.png"                     
[20] "h6_diagnostic_choice_prediction_accuracy_v7.png"             

21 Session information

Show code
sessionInfo()
R version 4.4.2 (2024-10-31)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=es_ES.UTF-8       LC_NUMERIC=C               LC_TIME=es_ES.UTF-8        LC_COLLATE=es_ES.UTF-8    
 [5] LC_MONETARY=es_ES.UTF-8    LC_MESSAGES=es_ES.UTF-8    LC_PAPER=es_ES.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=es_ES.UTF-8 LC_IDENTIFICATION=C       

time zone: Europe/Madrid
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] fs_1.6.6        scales_1.4.0    knitr_1.51      broom_1.0.7     vroom_1.6.5     lubridate_1.9.3 forcats_1.0.0  
 [8] stringr_1.5.2   dplyr_1.1.4     purrr_1.1.0     readr_2.1.6     tidyr_1.3.1     tibble_3.3.0    ggplot2_4.0.0  
[15] tidyverse_2.0.0

loaded via a namespace (and not attached):
 [1] generics_0.1.4     lattice_0.22-5     stringi_1.8.7      hms_1.1.3          digest_0.6.37      magrittr_2.0.4    
 [7] evaluate_1.0.5     grid_4.4.2         timechange_0.3.0   RColorBrewer_1.1-3 fastmap_1.2.0      Matrix_1.7-1      
[13] jsonlite_2.0.0     backports_1.5.0    mgcv_1.9-3         textshaping_1.0.4  cli_3.6.5          rlang_1.1.6       
[19] crayon_1.5.3       splines_4.4.2      bit64_4.5.2        withr_3.0.2        yaml_2.3.10        tools_4.4.2       
[25] parallel_4.4.2     tzdb_0.4.0         vctrs_0.6.5        R6_2.6.1           lifecycle_1.0.4    htmlwidgets_1.6.4 
[31] bit_4.5.0          ragg_1.5.0         pkgconfig_2.0.3    pillar_1.11.1      gtable_0.3.6       glue_1.8.0        
[37] systemfonts_1.3.1  xfun_0.53          tidyselect_1.2.1   rstudioapi_0.17.1  farver_2.1.2       nlme_3.1-167      
[43] htmltools_0.5.8.1  labeling_0.4.3     rmarkdown_2.30     compiler_4.4.2     S7_0.2.0