1 1. Business context and analytical objectives

Multigame ticket packages allow the Portland Trail Blazers to offer fans several games in a single purchase while varying seat location, ticket price, and promotional benefits. In the case setting, declining attendance and difficulties retaining premium-seat customers made package design an important commercial decision. The organization needed to identify offers that customers valued while maintaining price discipline and controlling the costs of package features.

The case describes a conjoint survey of former multigame or season-ticket buyers who were not current season-ticket holders. Of 960 contacted customers, 204 provided valid responses; each respondent faced 20 conjoint tasks. The project workbook contains attribute-level part-worth utility estimates. Accordingly, the present analysis evaluates the supplied utility model and its implications.

The full project addresses consumer preferences, willingness-to-pay, constrained product design, and sensitivity to assumptions. Module 2 concentrates on the first question:

Which package attributes and attribute levels contribute most strongly to stated consumer utility, and what does the estimated preference structure imply for package design?

The analysis has five objectives:

  1. Validate the part-worth inputs generated by Module 1 against the expected attribute design.
  2. Evaluate the utility ordering and magnitude of within-attribute differences.
  3. Calculate range-based relative attribute importance.
  4. Apply the additive utility model to clearly specified, feasible ticket-package profiles.
  5. Check the arithmetic and interpretation of model results before the monetary valuation and decision modules.

2 2. Data inputs and utility-model specification

2.1 2.1 Input provenance and analytical unit

The analysis reads data/processed/partworth_levels.csv, exported by Module 1 — Data Input, Validation & Exploratory Analysis from Portland Trail Blazers_Keman_Xiang.xlsx. Module 1 reads the Table 1. Utility score data. and Product Analysis worksheets, aligns attribute levels with the reported utilities, and checks the numerical inputs. The Excel workbook remains the source of the project values; the CSV provides a consistent, validated interface between R modules.

Each row represents one attribute level, not a consumer, transaction, or choice observation. A ticket package is a combination of four levels, one from each attribute.

Attribute Levels Interpretation
Number of Games 3, 6, 10 Size of the multigame package
Ticket Price $15, $25, $35, $60 Price per seat per game
Ticket Location 300 behind baskets, 300 corners, 300 midcourt, 200 midcourt Seating quality and location
Promotional Item Playoff priority, hot dog and soda, apparel, restaurant certificate, none Additional package benefit

The case restricts 200-level midcourt seats to a minimum price of $60 and 300-level midcourt seats to a minimum price of $25. Those restrictions matter whenever profiles are compared; the comprehensive feasible-set and cost analysis belongs to Module 4.

2.2 2.2 Additive part-worth utility

For package \(j\), the project uses an additive systematic utility specification:

\[ V_j = u_{G,g(j)} + u_{P,p(j)} + u_{L,l(j)} + u_{M,m(j)}, \]

where \(u_{G,g(j)}\), \(u_{P,p(j)}\), \(u_{L,l(j)}\), and \(u_{M,m(j)}\) denote the reported part-worths for the package’s game count, ticket price, location, and promotion. The model combines one utility from each attribute; it does not add every level within an attribute.

Under this specification, a difference in the utility of two packages equals the sum of the part-worth differences for the attributes that change. For example, changing only the promotion from none to hot dog and soda changes systematic utility by \(u_{\mathrm{hotdog}}-u_{\mathrm{none}}\), regardless of the unchanged attributes. This is an additive-model implication, not evidence that interactions are absent in consumer behavior.

Part-worth utilities are on a relative scale. A negative utility is not a negative probability, and a positive total utility is not a purchase probability or a dollar value. An additive constant shared by all profiles does not affect their ranking. No calibrated choice probability, demand forecast, or profit estimate is produced in this module.

3 3. Import and validate the conjoint inputs

3.1 3.1 Load the Module 1 dataset

The following code reads the validated input, preserves the attribute-level labels, and converts numeric columns explicitly. Using the same input file as subsequent modules avoids separate copies of utility values scattered across scripts.

partworth <- utils::read.csv(processed_path, stringsAsFactors = FALSE,
                             check.names = FALSE, na.strings = c("", "NA"))
required_cols <- c("attribute", "level_id", "level_label", "utility", "product_level")
assert(all(required_cols %in% names(partworth)),
       paste("Required fields missing:", paste(setdiff(required_cols, names(partworth)), collapse = ", ")))
partworth$utility <- as.numeric(partworth$utility)
partworth$level_id <- as.integer(partworth$level_id)
partworth$product_level <- as.character(partworth$product_level)
attribute_order <- c("Number of Games", "Ticket Price", "Ticket Location", "Promotional Item")
partworth <- partworth |>
  arrange(match(attribute, attribute_order), level_id)
knitr::kable(partworth[, c("attribute", "level_id", "product_level", "utility")],
             digits = 5, caption = "Conjoint utility inputs, by attribute and level")
Conjoint utility inputs, by attribute and level
attribute level_id product_level utility
Number of Games 1 3 0.03257
Number of Games 2 6 0.24383
Number of Games 3 10 -0.27640
Ticket Price 1 15 0.65646
Ticket Price 2 25 0.22011
Ticket Price 3 35 0.12600
Ticket Price 4 60 -1.00257
Ticket Location 1 300 level, behind the baskets -0.73169
Ticket Location 2 300 level, on the corners -0.43716
Ticket Location 3 300 level, midcourt 0.15736
Ticket Location 4 200 level, midcourt 1.01148
Promotional Item 1 Priority for home playoff tickets 0.12511
Promotional Item 2 Hot dog and soda with each ticket 0.17428
Promotional Item 3 Trail Blazer apparel (hat, jersey, etc.) 0.00158
Promotional Item 4 $20 gift certificate for popular local restaurant 0.01689
Promotional Item 5 No promotional item -0.31786

Interpretation. The input contains 16 part-worths distributed across 4 attributes. Each number expresses the relative contribution of one attribute level to a package’s systematic utility. The numerical inputs are estimates supplied in the case materials, not independent observations available for a new coefficient-estimation procedure.

3.2 3.2 Structural and numerical checks

The validation checks below enforce the expected experimental design, unique level identifiers, finite utility estimates, and the reported level values. These checks protect the remaining calculations against a changed or incomplete input file. The approximate zero-sum check describes the reported utility coding; it does not establish the underlying estimation method.

expected_counts <- c("Number of Games" = 3L, "Ticket Price" = 4L,
                     "Ticket Location" = 4L, "Promotional Item" = 5L)
actual_counts <- table(factor(partworth$attribute, levels = attribute_order))
price_data <- partworth |> filter(attribute == "Ticket Price") |>
  mutate(price_usd = suppressWarnings(as.numeric(product_level))) |>
  arrange(price_usd)
game_data <- partworth |> filter(attribute == "Number of Games") |>
  mutate(games = suppressWarnings(as.numeric(product_level))) |>
  arrange(games)

validation <- data.frame(
  check = c("Four attributes and 16 levels", "Correct number of levels per attribute",
            "Unique attribute-level identifiers", "All utilities are finite",
            "Expected game-count levels", "Expected ticket-price levels",
            "Price utility decreases with price", "Part-worths approximately centered by attribute"),
  passed = c(nrow(partworth) == 16L && length(unique(partworth$attribute)) == 4L,
             identical(as.integer(actual_counts), as.integer(expected_counts)),
             !anyDuplicated(partworth[, c("attribute", "level_id")]) &&
               !anyDuplicated(partworth[, c("attribute", "product_level")]),
             all(is.finite(partworth$utility)),
             identical(game_data$games, c(3, 6, 10)),
             identical(price_data$price_usd, c(15, 25, 35, 60)),
             all(diff(price_data$utility) < 0),
             all(abs(tapply(partworth$utility, partworth$attribute, sum)) < 1e-4)))
knitr::kable(validation, caption = "Module 2 input validation")
Module 2 input validation
check passed
Four attributes and 16 levels TRUE
Correct number of levels per attribute TRUE
Unique attribute-level identifiers TRUE
All utilities are finite TRUE
Expected game-count levels TRUE
Expected ticket-price levels TRUE
Price utility decreases with price TRUE
Part-worths approximately centered by attribute TRUE
assert(all(validation$passed), "Input validation failed; inspect the table before proceeding.")

Validation result. All 8 input checks passed. The ticket-price utility is monotonically decreasing over the four observed price levels. This is an arithmetic and data-integrity result; it should not be confused with a test of predictive accuracy or statistical significance.

4 4. Attribute-level preference structure

4.1 4.1 Rank levels within each attribute

A level’s part-worth is most informative when compared with other levels of the same attribute. The next calculation identifies the most and least preferred level and the difference between them. The difference is the largest change in systematic utility available within that attribute, holding the other attributes fixed.

level_rankings <- partworth |>
  group_by(attribute) |>
  arrange(desc(utility), .by_group = TRUE) |>
  mutate(preference_rank = row_number(),
         gain_vs_least_preferred = utility - min(utility)) |>
  ungroup() |>
  select(attribute, preference_rank, product_level, utility, gain_vs_least_preferred)
knitr::kable(level_rankings, digits = 5,
             caption = "Preference ranking and utility gain relative to the least-preferred level")
Preference ranking and utility gain relative to the least-preferred level
attribute preference_rank product_level utility gain_vs_least_preferred
Number of Games 1 6 0.24383 0.52023
Number of Games 2 3 0.03257 0.30897
Number of Games 3 10 -0.27640 0.00000
Promotional Item 1 Hot dog and soda with each ticket 0.17428 0.49214
Promotional Item 2 Priority for home playoff tickets 0.12511 0.44297
Promotional Item 3 $20 gift certificate for popular local restaurant 0.01689 0.33475
Promotional Item 4 Trail Blazer apparel (hat, jersey, etc.) 0.00158 0.31944
Promotional Item 5 No promotional item -0.31786 0.00000
Ticket Location 1 200 level, midcourt 1.01148 1.74317
Ticket Location 2 300 level, midcourt 0.15736 0.88905
Ticket Location 3 300 level, on the corners -0.43716 0.29453
Ticket Location 4 300 level, behind the baskets -0.73169 0.00000
Ticket Price 1 15 0.65646 1.65903
Ticket Price 2 25 0.22011 1.22268
Ticket Price 3 35 0.12600 1.12857
Ticket Price 4 60 -1.00257 0.00000

The best and worst level of each attribute summarize the central preference pattern without treating the utility scores as dollar values.

attribute_extremes <- partworth |>
  group_by(attribute) |>
  summarise(
    best_level = product_level[which.max(utility)],
    best_utility = max(utility),
    worst_level = product_level[which.min(utility)],
    worst_utility = min(utility),
    utility_range = max(utility) - min(utility),
    .groups = "drop") |>
  arrange(match(attribute, attribute_order))
knitr::kable(attribute_extremes, digits = 5,
             caption = "Most and least preferred levels within each attribute")
Most and least preferred levels within each attribute
attribute best_level best_utility worst_level worst_utility utility_range
Number of Games 6 0.24383 10 -0.27640 0.52023
Ticket Price 15 0.65646 60 -1.00257 1.65903
Ticket Location 200 level, midcourt 1.01148 300 level, behind the baskets -0.73169 1.74317
Promotional Item Hot dog and soda with each ticket 0.17428 No promotional item -0.31786 0.49214

Results and interpretation. A six-game package is preferred to both three-game and ten-game packages in the reported estimates: the six-game utility is 0.24383, versus 0.03257 for three games and -0.27640 for ten. The preference for six over ten games is a particularly important finding: larger bundles are not automatically more desirable.

For seating, 200-level midcourt has the highest part-worth, while 300-level behind baskets has the lowest. The difference between these extremes is 1.74317 utility units. The premium seat’s attractiveness must later be considered alongside its $60 minimum price.

Among promotions, hot dog and soda has the highest utility, while no promotional item has the lowest. The existence of a positive preference for a benefit does not establish that providing it is economically worthwhile; Module 3 compares incremental value with reported costs.

4.2 4.2 Visualize the part-worth profile

The chart presents each attribute separately, with a shared utility-unit interpretation. The horizontal zero line marks the reported centering reference, not an indifference threshold between buying and not buying.

plot_data <- partworth |>
  mutate(attribute = factor(attribute, levels = attribute_order),
         display_level = factor(paste(attribute, product_level, sep = " | "),
                                levels = paste(attribute, product_level, sep = " | ")))
p_partworth <- ggplot(plot_data, aes(x = reorder(display_level, utility), y = utility)) +
  geom_col(fill = "#315F7D", width = 0.7) +
  geom_hline(yintercept = 0, linewidth = 0.35, colour = "grey45") +
  coord_flip() +
  facet_wrap(~attribute, scales = "free_y", ncol = 2,
             labeller = label_value) +
  scale_x_discrete(labels = function(x) sub("^.* \\| ", "", x)) +
  labs(x = NULL, y = "Part-worth utility") +
  theme_minimal(base_size = 11) +
  theme(strip.text = element_text(face = "bold"))
print(p_partworth)
Part-worth utility by attribute and level

Part-worth utility by attribute and level

save_figure(p_partworth, "02_partworth_preference_structure.png", 12, 9)

Figure interpretation. The chart reveals three distinct patterns. First, utility decreases with ticket price, as expected for a monetary cost attribute. Second, the preference for six games over ten games suggests that package size may involve a trade-off between access and commitment. Third, seating quality creates a large utility difference, whereas promotions create a smaller but still potentially meaningful incremental benefit. These interpretations concern the stated-preference model and do not establish causal mechanisms.

5 5. Relative attribute importance

5.1 5.1 Range-based importance calculation

To compare attributes, the project calculates the utility range for each attribute and divides it by the sum of the four ranges:

\[ R_a = \max_{\ell \in a}u_{a\ell}-\min_{\ell \in a}u_{a\ell}, \qquad I_a=\frac{R_a}{\sum_b R_b}\times100\%. \]

This measure answers a precise question: How much of the total observed part-worth range comes from each attribute, given the levels included in this study? It is not a regression coefficient, a significance test, or a general population-level importance estimate. Changing the price range or available seat locations could change the importance percentages.

importance <- partworth |>
  group_by(attribute) |>
  summarise(n_levels = n(),
            min_utility = min(utility),
            max_utility = max(utility),
            utility_range = max_utility - min_utility,
            .groups = "drop") |>
  mutate(relative_importance = utility_range / sum(utility_range),
         importance_pct = 100 * relative_importance) |>
  arrange(desc(relative_importance))
assert(abs(sum(importance$relative_importance) - 1) < 1e-12,
       "Relative importance must sum to one.")
knitr::kable(importance, digits = 5,
             caption = "Range-based relative importance of ticket-package attributes")
Range-based relative importance of ticket-package attributes
attribute n_levels min_utility max_utility utility_range relative_importance importance_pct
Ticket Location 4 -0.73169 1.01148 1.74317 0.39487 39.48675
Ticket Price 4 -1.00257 0.65646 1.65903 0.37581 37.58078
Number of Games 3 -0.27640 0.24383 0.52023 0.11784 11.78439
Promotional Item 5 -0.31786 0.17428 0.49214 0.11148 11.14808
p_importance <- ggplot(importance, aes(x = reorder(attribute, importance_pct), y = importance_pct)) +
  geom_col(fill = "#315F7D", width = 0.65) +
  geom_text(aes(label = sprintf("%.2f%%", importance_pct)), hjust = -0.12, size = 3.7) +
  coord_flip() +
  scale_y_continuous(limits = c(0, max(importance$importance_pct) * 1.20),
                     expand = expansion(mult = c(0, 0))) +
  labs(x = NULL, y = "Relative importance (%)") +
  theme_minimal(base_size = 11)
print(p_importance)
Relative attribute importance (percent of total part-worth range)

Relative attribute importance (percent of total part-worth range)

save_figure(p_importance, "02_attribute_importance.png", 9, 5.5)

Results and interpretation. Ticket location accounts for 39.49% of the total utility range, followed by ticket price at 37.58%. Together, these attributes account for 77.07% of the total range. Number of games contributes 11.78%, and promotions contribute 11.15%.

This pattern makes seat location and price central to package differentiation. However, the lower percentage for promotions should not be interpreted as evidence that giveaways are unnecessary. Relative importance measures the span of utility values within an attribute, whereas an economically attractive giveaway depends on its incremental value and incremental cost.

5.2 5.2 Compare importance with the Module 1 output

Module 1 also computes attribute importance as part of exploratory analysis. The comparison below checks that both modules return the same values from the same validated input. This is an independent computational consistency check.

module1_file <- file.path(output_tables, "01_attribute_importance.csv")
if (file.exists(module1_file)) {
  module1_importance <- read.csv(module1_file, stringsAsFactors = FALSE)
  assert(all(c("attribute", "relative_importance") %in% names(module1_importance)),
         "Module 1 importance output has unexpected columns.")
  comparison <- importance |>
    select(attribute, module2_importance = relative_importance) |>
    left_join(module1_importance |>
                select(attribute, module1_importance = relative_importance),
              by = "attribute") |>
    mutate(absolute_difference = abs(module2_importance - module1_importance))
  assert(!anyNA(comparison$module1_importance), "Missing attribute in Module 1 importance file.")
  assert(all(comparison$absolute_difference < 1e-10),
         "Module 1 and Module 2 importance calculations disagree.")
  knitr::kable(comparison, digits = 10,
               caption = "Consistency of attribute importance across Modules 1 and 2")
} else {
  comparison <- NULL
  cat("Module 1 importance output is not present; direct input validation and Module 2 checks remain active.")
}
Consistency of attribute importance across Modules 1 and 2
attribute module2_importance module1_importance absolute_difference
Ticket Location 0.3948675 0.3948675 0
Ticket Price 0.3758078 0.3758078 0
Number of Games 0.1178439 0.1178439 0
Promotional Item 0.1114808 0.1114808 0

Interpretation. All four importance values agree with Module 1 within a tolerance of 1e-10; the largest absolute difference is 3.885781e-16. This agreement helps detect accidental changes to calculation code or data files.

6 6. Marginal utility differences and attribute trade-offs

6.1 6.1 Selected within-attribute comparisons

Because utilities are additive, a change from level \(a\) to level \(b\) within one attribute changes package utility by:

\[ \Delta V=u_b-u_a. \]

The following comparisons quantify several managerially meaningful changes. They are measured in utility units, not dollars; monetary valuation is addressed in Module 3.

lookup_u <- function(attribute_name, level) {
  value <- partworth$utility[partworth$attribute == attribute_name &
                               partworth$product_level == level]
  assert(length(value) == 1L && is.finite(value),
         paste("Cannot uniquely identify", attribute_name, "level", level))
  value
}

# Labels are retrieved from the input using source level IDs, not manually typed.
label_by_id <- function(attribute_name, id) {
  value <- partworth$product_level[partworth$attribute == attribute_name &
                                    partworth$level_id == id]
  assert(length(value) == 1L, "Level ID is missing or duplicated.")
  value
}

changes <- data.frame(
  attribute = c("Number of Games", "Number of Games", "Ticket Price", "Ticket Price",
                "Ticket Location", "Promotional Item"),
  from_id = c(1, 2, 2, 4, 1, 5),
  to_id   = c(2, 3, 1, 3, 4, 2), stringsAsFactors = FALSE)
changes$from_level <- mapply(label_by_id, changes$attribute, changes$from_id)
changes$to_level <- mapply(label_by_id, changes$attribute, changes$to_id)
changes$from_utility <- mapply(lookup_u, changes$attribute, changes$from_level)
changes$to_utility <- mapply(lookup_u, changes$attribute, changes$to_level)
changes$delta_utility <- changes$to_utility - changes$from_utility
knitr::kable(changes[, c("attribute", "from_level", "to_level", "delta_utility")],
             digits = 5, caption = "Selected changes in systematic utility")
Selected changes in systematic utility
attribute from_level to_level delta_utility
Number of Games 3 6 0.21126
Number of Games 6 10 -0.52023
Ticket Price 25 15 0.43635
Ticket Price 60 35 1.12857
Ticket Location 300 level, behind the baskets 200 level, midcourt 1.74317
Promotional Item No promotional item Hot dog and soda with each ticket 0.49214

Results and interpretation. Moving from three games to six increases utility by 0.21126; moving from six to ten reduces utility by 0.52023. Reducing price from $25 to $15 increases utility by 0.43635, while the larger price reduction from $60 to $35 increases utility by 1.12857. These price changes have different dollar magnitudes and must not be interpreted as constant marginal utility per dollar.

The improvement from 300-level behind baskets to 200-level midcourt raises utility by 1.74317, but the latter seat requires a higher minimum price. Likewise, adding hot dog and soda instead of no promotion increases utility by 0.49214; whether that increase justifies the reported cost is a separate economic question.

7 7. Package-level utility and feasible-profile examples

7.1 7.1 Apply the additive utility model

To demonstrate how the model evaluates actual package configurations, the project generates all combinations of attribute levels and applies the case’s price restrictions. This is a design-space calculation, not a set of survey observations. The full package-level decision and cost analysis is reserved for Module 4.

The unrestricted design has \(3\times4\times4\times5=240\) combinations. Under the two minimum-price rules, 180 combinations remain feasible.

get_levels <- function(attribute_name, prefix) {
  x <- partworth |>
    filter(attribute == attribute_name) |>
    arrange(level_id) |>
    select(level_id, product_level, utility)
  names(x) <- paste0(prefix, c("_id", "_level", "_utility"))
  x
}
g <- get_levels("Number of Games", "games")
p <- get_levels("Ticket Price", "price")
l <- get_levels("Ticket Location", "seat")
m <- get_levels("Promotional Item", "promo")

# IDs follow the order of the input workbook's attribute-level tables.
all_packages <- tidyr::crossing(g, p, l, m) |>
  mutate(games = suppressWarnings(as.numeric(games_level)),
         price_usd = suppressWarnings(as.numeric(price_level)),
         feasible = !(seat_id == 4L & price_usd < 60) &
                    !(seat_id == 3L & price_usd < 25),
         total_utility = games_utility + price_utility + seat_utility + promo_utility)
assert(nrow(all_packages) == 240L, "The unrestricted design must contain 240 packages.")
assert(sum(all_packages$feasible) == 180L, "The feasible design must contain 180 packages.")
feasible_packages <- all_packages |> filter(feasible)
knitr::kable(data.frame(
  design = c("All possible combinations", "Feasible combinations", "Excluded combinations"),
  packages = c(nrow(all_packages), nrow(feasible_packages), sum(!all_packages$feasible))),
  caption = "Package design space under minimum-price restrictions")
Package design space under minimum-price restrictions
design packages
All possible combinations 240
Feasible combinations 180
Excluded combinations 60

Interpretation. The 60 excluded configurations violate a minimum-price rule. The 180 remaining combinations are possible product designs, not 180 respondents, 180 transactions, or 180 independently estimated utilities. All package utilities are calculated from the same 16 supplied part-worth values.

7.2 7.2 Compare selected feasible packages

The next table illustrates three distinct design choices: the highest-utility feasible package; the highest-utility package without a promotion; and the highest-utility package at the $60 price level. These are descriptive comparisons of systematic utility, not claims about sales, profit, or market share. The sorting rules are explicit so the results can be audited.

rank_packages <- function(x) {
  x |>
    arrange(desc(total_utility), games_id, price_id, seat_id, promo_id)
}
examples <- bind_rows(
  rank_packages(feasible_packages) |> slice(1) |> mutate(scenario = "Highest feasible utility"),
  rank_packages(feasible_packages |> filter(promo_id == 5L)) |>
    slice(1) |> mutate(scenario = "Highest utility without promotion"),
  rank_packages(feasible_packages |> filter(price_usd == 60)) |>
    slice(1) |> mutate(scenario = "Highest utility at $60")) |>
  select(scenario, games_level, price_level, seat_level, promo_level, total_utility)
knitr::kable(examples, digits = 5,
             caption = "Illustrative feasible package configurations")
Illustrative feasible package configurations
scenario games_level price_level seat_level promo_level total_utility
Highest feasible utility 6 25 300 level, midcourt Hot dog and soda with each ticket 0.79558
Highest utility without promotion 6 25 300 level, midcourt No promotional item 0.30344
Highest utility at $60 6 60 200 level, midcourt Hot dog and soda with each ticket 0.42702

Results and interpretation. The highest-utility feasible package combines 6 games, a $25 ticket price, 300 level, midcourt seating, and Hot dog and soda with each ticket, with systematic utility 0.79558. The highest-utility package without a promotion has utility 0.30344, highlighting the preference benefit of adding a promotional feature when the other attributes can also be selected optimally. The $60 comparison illustrates the effect of allowing premium seating at a higher price.

The best-utility package should not be described as the revenue- or profit-maximizing product. The available part-worth table does not provide the purchase volumes, calibrated outside option, or complete cost-frequency definitions required for that conclusion.

7.3 7.3 Verify the utility decomposition

A transparent utility calculation should reproduce the total exactly from its four attribute contributions. The following check evaluates this identity for every feasible package and displays the components of the highest-utility profile.

component_check <- feasible_packages |>
  mutate(component_sum = games_utility + price_utility + seat_utility + promo_utility,
         arithmetic_error = abs(total_utility - component_sum))
assert(all(component_check$arithmetic_error < 1e-12),
       "A package total differs from the sum of its four components.")
best <- rank_packages(feasible_packages) |> slice(1)
decomposition <- data.frame(
  component = c("Number of games", "Ticket price", "Seat location", "Promotional item", "Total"),
  utility = c(best$games_utility, best$price_utility,
              best$seat_utility, best$promo_utility, best$total_utility))
knitr::kable(decomposition, digits = 5,
             caption = "Utility decomposition of the highest-utility feasible package")
Utility decomposition of the highest-utility feasible package
component utility
Number of games 0.24383
Ticket price 0.22011
Seat location 0.15736
Promotional item 0.17428
Total 0.79558

Interpretation. The four contributions sum to 0.79558 with a maximum arithmetic discrepancy below 0e+00. This verifies the implemented additive arithmetic. It does not test whether the additive model predicts real purchasing decisions.

8 8. Model interpretation and limitations

The utility structure has three direct implications for ticket-package design. First, price and seat location produce the largest changes in stated utility across the available levels. Second, the six-game option has greater utility than either the smaller or larger package, so increasing package size is not automatically an improvement. Third, promotional benefits produce positive incremental utility relative to no promotion, but their commercial attractiveness depends on cost and willingness-to-pay, not on utility alone.

The analysis also has clear boundaries:

  • Supplied aggregate estimates: The workbook contains part-worth estimates, not respondent-level choice records. It does not support independent estimation of coefficients, standard errors, heterogeneity, or out-of-sample predictive performance.
  • Additive specification: The package utility calculation assumes contributions add without interactions. The data provided here do not allow interaction effects to be estimated or tested.
  • Relative scale: Utility levels and their differences can rank profiles within this model; they are not automatically monetary values, purchase probabilities, or expected demand.
  • Level-dependent importance: Range-based importance depends on the levels included in the design and is not an intrinsic, context-free property of an attribute.
  • Business feasibility: Minimum ticket prices restrict the feasible set. Package utility alone does not account for seat inventory, demand, cannibalization, allocated costs, or promotional expense.
  • Historical and targeted sample: The case concerns a particular period and a survey of previous ticket purchasers. Results should not be presented as current market evidence or as representative of all potential fans.

9 9. Export analytical outputs

The output files below provide a stable interface for Module 3’s willingness-to-pay calculations and Module 4’s constrained package evaluation. They contain derived results, not additional observed respondents. Each table is generated from the validated Module 1 input.

save_table(level_rankings, "02_level_preference_rankings.csv")
save_table(attribute_extremes, "02_attribute_extremes.csv")
save_table(importance, "02_attribute_importance.csv")
save_table(changes, "02_marginal_utility_comparisons.csv")
save_table(feasible_packages, "02_feasible_package_utilities.csv")
save_table(examples, "02_illustrative_packages.csv")
save_table(validation, "02_input_validation.csv")

manifest <- data.frame(
  output_type = c(rep("Table", 7), rep("Figure", 2)),
  file = c("02_level_preference_rankings.csv", "02_attribute_extremes.csv",
           "02_attribute_importance.csv", "02_marginal_utility_comparisons.csv",
           "02_feasible_package_utilities.csv", "02_illustrative_packages.csv",
           "02_input_validation.csv", "02_partworth_preference_structure.png",
           "02_attribute_importance.png"))
knitr::kable(manifest, caption = "Module 2 output files (outputs/tables and outputs/figures)")
Module 2 output files (outputs/tables and outputs/figures)
output_type file
Table 02_level_preference_rankings.csv
Table 02_attribute_extremes.csv
Table 02_attribute_importance.csv
Table 02_marginal_utility_comparisons.csv
Table 02_feasible_package_utilities.csv
Table 02_illustrative_packages.csv
Table 02_input_validation.csv
Figure 02_partworth_preference_structure.png
Figure 02_attribute_importance.png

10 10. Conclusions and next analytical steps

The conjoint utility model identifies ticket location and price as the largest contributors to observed preference variation within the supplied design. The six-game configuration is preferred to three- and ten-game alternatives, and the most preferred promotional item is hot dog and soda. Applying the additive specification to the feasible product space makes these patterns actionable for comparing package designs while maintaining the distinction between consumer utility and economic performance.

Module 3 — Willingness-to-Pay & Attribute Trade-off Analysis will translate selected utility differences into carefully defined monetary comparisons. Because the price part-worths are not linear across the four observed price levels, the next module must distinguish local price slopes from piecewise price-utility valuation and evaluate how these choices affect interpretation. Module 4 will integrate feasible package utilities with pricing and cost considerations.

11 References and data provenance

  • Wilcox, Ronald T. Portland Trail Blazers. Darden Business Publishing, case UV2971 (revised April 17, 2017). Case setting, conjoint survey description, ticket attributes, price restrictions, and utility inputs.
  • Xiang, Keman. Portland Trail Blazers_Keman_Xiang.xlsx. Numerical source for the project’s utility and product-attribute inputs.
  • Xiang, Keman. Assignment 3. Project analysis of attribute importance, ticket design, and promotional benefits.
  • Conjoint-2023; Week 3 Conjoint Analysis (II) 2023; Dummy_variables. Methodological course materials.