
<!-- README.md is generated from README.Rmd. Please edit that file -->

# masc: Multi-Attribute Search and Choice Model in R

<!-- badges: start -->

[![Lifecycle:
experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)
<!-- badges: end -->

## Overview

The `masc` package implements the Multi-Attribute Search and Choice
(MASC) model, a hierarchical Bayesian framework for understanding how
people make decisions between options with multiple attributes. Based on
the work by Gluth, Deakin, & Rieskamp (2026), this package simulates:

- Information search patterns in multi-attribute decisions
- Belief updating about attribute values (univariate, or multivariate
  over correlated attributes via MASC-C)
- Choice dynamics and decision termination
- The interplay between attention and valuation

## Installation

``` r
# Install from CRAN
install.packages("masc")

# Or the development version from GitHub
devtools::install_github("kiante-fernandez/masc")
```

## Basic Usage

### Simple Random Trials

``` r
library(masc)

# Generate 5 random trials
results <- rMASC(
  n = 5,
  w = c(0.5, 0.3, 0.2)  # weights for attributes
)

# View results
print(results$results)
```

### Custom Attribute Values

``` r
# Create trial data
trial_data <- data.frame(
  # Option 1's attributes across 3 trials
  opt1_att1 = c(4.5, 4.2, 4.8),  # Attribute 1 values
  opt1_att2 = c(3.2, 3.5, 3.1),  # Attribute 2 values
  opt1_att3 = c(2.8, 2.9, 2.7),  # Attribute 3 values
  # Option 2's attributes across 3 trials
  opt2_att1 = c(3.8, 3.9, 3.7),  # Attribute 1 values
  opt2_att2 = c(4.1, 4.0, 4.2),  # Attribute 2 values
  opt2_att3 = c(3.1, 3.3, 3.0)   # Attribute 3 values
)

# Run model with custom data
results <- rMASC(
  data = trial_data,
  w = c(0.5, 0.3, 0.2)  # weights for attributes
)
```

### Correlated Attributes (MASC-C)

By default attributes are treated as independent, reproducing the
original MASC model. Supplying a correlation structure switches on the
multivariate **MASC-C** belief update, in which observing one attribute
updates beliefs about correlated attributes (“belief spread”). Positive
correlations speed up decisions; negative correlations slow them down.

``` r
# Decision maker exploits a positive correlation structure (rho = 0.6)
results <- rMASC(
  n = 100,
  w = c(0.5, 0.3, 0.2),
  Sigma_true   = 0.6,   # stimuli are positively correlated
  Sigma_belief = 0.6    # and the agent knows it (matched beliefs)
)

# Full correlation matrices are also accepted, and the agent's beliefs may
# differ from the true environment (belief-environment mismatch):
Sigma <- matrix(c(1.0, 0.5, -0.3,
                  0.5, 1.0,  0.0,
                 -0.3, 0.0,  1.0), 3, 3, byrow = TRUE)
results <- rMASC(n = 100, w = c(0.5, 0.3, 0.2),
                 Sigma_true = Sigma, Sigma_belief = 0)  # agent assumes independence
```

When `Sigma_belief` is diagonal (or `0`) the model reduces exactly to
the original univariate MASC update.

### Example use

``` r
library(masc)
library(ggplot2)
library(dplyr)
library(tidyr)
library(patchwork)

# Set simulation parameters
params <- list(
  n = 100,              # Number of trials
  n_options = 2,        # Number of choice options
  n_attributes = 3,     # Number of attributes per option
  w = c(0.5, 0.3, 0.2), # Weights for each attribute
  sigma = 1,            # Sampling noise
  alpha = 3,            # Search sensitivity
  delta = 0.01,         # Threshold increment
  theta = 0.01,         # Initial threshold
  lambda = 1,           # Prior precision
  max_steps = 100       # Maximum fixations allowed
)

# Run simulation
set.seed(123)
results <- do.call(rMASC, params)

# Process choice proportions
choice_props <- results$results %>%
  mutate(Choice = factor(response, labels = c("Option1", "Option2"))) %>%
  group_by(Choice) %>%
  summarise(Proportion = n()/100)

# Process fixation data
total_counts <- numeric(params$n_options * params$n_attributes)
for(trial in results$raw) {
  fix_seq <- trial$fix_sequence
  trial_counts <- table(factor(fix_seq, 
                               levels = 1:(params$n_options * params$n_attributes)))
  total_counts <- total_counts + as.numeric(trial_counts)
}

# Create Option-Attribute-Pair (OAP) data
all_props <- total_counts / sum(total_counts)
oap_data <- data.frame(Proportion = all_props) %>%
  mutate(oap_number = 1:length(all_props)) %>%
  mutate(
    Option = paste0("Opt", ceiling(oap_number/3)),
    Attribute = paste0("Att", ((oap_number-1) %% 3) + 1)
  ) %>%
  select(Option, Attribute, Proportion)

# Define consistent theme
theme_masc <- function() {
  theme_classic() +
    theme(
      plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
      axis.title = element_text(size = 12, face = "bold"),
      axis.text = element_text(size = 11),
      legend.position = "bottom"
    )
}

# Create visualizations
# 1. Fixation Distribution
p1 <- ggplot(results$results, aes(x = rt, fill = factor(response))) +
  geom_histogram(position = "dodge", bins = 30, color = "white") +
  scale_fill_manual(values = c("#2171B5", "#CB181D"),
                    labels = c("Choice = Option1", "Choice = Option2")) +
  labs(title = "Number of Fixations", x = "Number of Fixations", y = "Count") +
  theme_masc()

# 2. Choice Probability
p2 <- ggplot(choice_props, aes(x = "", y = Proportion, fill = Choice)) +
  geom_bar(stat = "identity", width = 0.6) +
  geom_text(aes(label = sprintf("%.2f", Proportion)), 
            position = position_stack(vjust = 0.5),
            color = "white", 
            size = 4) +
  scale_fill_manual(values = c("#2171B5", "#CB181D")) +
  labs(title = "Choice Probability", y = "Proportion", x = "Response") +
  theme_masc() +
  scale_y_continuous(limits = c(0, 1), breaks = seq(0, 1, 0.1))

# 3. OAP Heatmap
p3 <- ggplot(oap_data, aes(x = Attribute, y = Option, fill = Proportion)) +
  geom_tile(color = "white") +
  scale_fill_distiller(palette = "Blues", direction = 1) +
  geom_text(aes(label = sprintf("%.4f", Proportion)), size = 4) +
  labs(title = "Fixation Proportions by Option-Attribute Pair") +
  theme_masc()

# Combine plots
combined_plot <- p1 + p2 + p3 +
  plot_layout(ncol = 2) +
  plot_annotation(
    title = "Example: MASC Model Simulation Results",
    theme = theme(plot.title = element_text(size = 20, face = "bold", hjust = 0.5))
  )

# Display plot
print(combined_plot)
```

## Function Parameters

The `rMASC()` function accepts the following parameters:

- `data`: Optional data frame with trial-wise attribute values
- `n`: Number of trials to generate when `data` is NULL (default: 1)
- `n_options`: Number of choice options (default: 2)
- `n_attributes`: Number of attributes per option (default: 3)
- `w`: Attribute weights summing to 1 (if NULL, randomly generated)
- `sigma`: Standard deviation of sampling noise (default: 1)
- `alpha`: Search rule sensitivity (default: 3)
- `delta`: Decision threshold increase per fixation (default: 0.01)
- `theta`: Initial decision threshold (default: 0.01)
- `lambda`: Precision of prior beliefs about attributes (default: 1)
- `max_steps`: Maximum number of fixations allowed (default: 100)
- `Sigma_true`: Correlation/covariance structure of the generated
  stimuli — a matrix, or a single number giving a uniform off-diagonal
  correlation (default: `NULL`, i.e. independent attributes). Ignored
  when `data` is supplied.
- `Sigma_belief`: The decision maker’s assumed correlation structure
  between attributes. This enables the multivariate (“MASC-C”) belief
  update, where observing one attribute spreads information to
  correlated attributes. `NULL` (default) matches `Sigma_true`; `0`
  forces independent (original MASC) beliefs.

## Output Structure

The function returns a list containing:

- `results`: Data frame with trial-by-trial results including:
  - trial number
  - chosen option
  - best option
  - choice consistency
  - number of fixations (rt)
  - fixation proportions
- `weights`: Vector of attribute weights used
- `parameters`: List of model parameters used
- `raw`: List containing detailed raw data for each trial

The `raw` component contains a list where each element corresponds to a
trial and includes:

- `trial`: Trial number
- `response`: The option chosen by the model (1 to n_options)
- `best_option`: The option with the highest weighted value
- `correct`: Boolean indicating if response matches best_option
- `rt`: Number of fixations taken to reach a decision
- `x`: Matrix of true attribute values for all options
- `opt_values`: Vector of computed option values (weighted sums)
- `weights`: Vector of attribute weights used in this trial
- `sigma`: Sampling noise parameter used
- `alpha`: Search sensitivity parameter used
- `delta`: Threshold increment parameter used
- `theta`: Initial threshold parameter used
- `fix_sequence`: Vector showing the sequence of fixations made
- `prop_fix_opt`: Vector of proportions of fixations to each option
- `prop_fix_att`: Vector of proportions of fixations to each attribute

## Reference

Gluth, S., Deakin, J., & Rieskamp, J. (2026). A theory of multiattribute
search and choice. *Psychological Review*.
<https://doi.org/10.1037/rev0000614>

## License

This package is released under the MIT License.
