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:
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.
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.
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")| 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.
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")| 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 |
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.
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")| 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")| 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.
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
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.
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")| 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)
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.
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.")
}| 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.
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")| 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.
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")| 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.
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")| 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.
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")| 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.
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:
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)")| 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 |
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.
Portland Trail Blazers_Keman_Xiang.xlsx.
Numerical source for the project’s utility and product-attribute
inputs.