Van Westendorp Pricing Model

This quick example is designed to provide one approach to getting a sense of market pricing using consumer surveys. This approach uses the van Westendorp Pricing Model.

This model is based on asking four questions of consumers about a test product:

  1. At which price on this scale you are beginning to experience (test-product) as too cheap – so that you say “at this price the quality cannot be good”?

  2. At which price on this scale are you beginning to experience (test-product) as cheap?

  3. At which price on this scale are you beginning to experience (test-product) as expensive?

  4. At which price on this scale you are beginning to experience (test-product) as too expensive – so that you would never consider buying it yourself?

Using the consumer responses to these four questions, the cumulative distribution of responses can mapped against the prices develop 4 curves for analysis: Too Cheap (tch), Cheap (ch), Expensive (ex), Too Expensive (tex).


Please note, this example was based on the documentation and r package pricesensitivitymeter by Max Alletsee. This is for demonstration and example purposes only. If an actual pricing analysis is to be conducted, more research is necessary.



Sample Survey Data

For this example, the 500 survey responses are randomly generated. To get a sense for the data, the below table shows the survey responses across the four key variables: tch, ch, ex, tex. Note, given this data was randomly generated from a distribution, like any analysis, nonsensical responses are automatically dropped from inclusion (i.e. tch < ch < ex < tex).

tch ch ex tex
23.7 52.5 53.9 63.8
19.7 37.6 48.8 55.3
28.4 38.7 53.4 64.1
25.1 38.4 39.0 58.3
16.6 48.0 62.2 64.2
17.6 47.4 39.6 61.1
27.2 35.9 36.5 64.4
25.0 15.0 34.0 59.0
29.4 43.7 53.6 64.5
23.0 23.5 41.9 64.9


Cumulative Distribution Calculation

Using the survey responses, each variable is rank ordered from smallest to largest and a cumulative distribution is calculated for all responses. The cumulative distribution for each variable is then plotted against the price points. The below data snapshot demonstrates this idea.


price ecdf_toocheap ecdf_cheap ecdf_expensive ecdf_tooexpensive
12.3 0.9961240 1.0000000 0 0
13.0 0.9922481 1.0000000 0 0
13.5 0.9883721 1.0000000 0 0
14.1 0.9844961 1.0000000 0 0
14.5 0.9844961 0.9961240 0 0
14.6 0.9806202 0.9961240 0 0
14.9 0.9767442 0.9961240 0 0
15.0 0.9728682 0.9961240 0 0
15.1 0.9689922 0.9961240 0 0
15.6 0.9651163 0.9961240 0 0
16.0 0.9651163 0.9922481 0 0
16.2 0.9573643 0.9922481 0 0
16.3 0.9534884 0.9922481 0 0
16.4 0.9496124 0.9922481 0 0
16.5 0.9457364 0.9922481 0 0


Graphical Analysis & Interpretation

A quick look at the below figure helps to clarify the value of this analysis. In its simplest form, there four key points of note as stated by Allestee in the r pricesensitivitymeter documentation:

  1. Point of Marginal Cheapness (MGP): Below this price point, there are more respondents that consider the price as “too cheap” than respondents who consider it as “expensive” (intersection of “too cheap” and “expensive”). This is interpreted as the lower limit of the range of acceptable prices.

  2. Point of Marginal Expensiveness (MEP). Above this price point, there are more respondent that consider the price as “too expensive” than there are respondents who consider it as “cheap” (intersection of “cheap” and “too expensive”). This is interpreted as the upper limit of the range of acceptable prices.

  3. Indifference Price Point (IDP): The same number of respondents perceives the price as “cheap” and “expensive” (intersection of “cheap” and “expensive”). In van Westendorp’s interpretation, this is either the median price paid in the market or the price of an important market-leader.

  4. Optimal Price Point (OPP): The same number of respondents perceives the product as “too cheap” and “too expensive” (intersection of “too cheap” and “too expensive”). van Westendorp argues that this is the value for which the respondents’ resistance against the price is particularly low.




Van Westendorp Price Sensitivity Meter Analysis

Based on this analysis, this test product should be priced between $33.8-$51.3, has an optimal price point of $37.7, and an indifference price point of $41.8.




Replication with Code

The remainder of this page replicates the above analysis while presenting the associated R code. Enjoy.


Model Code


library(pricesensitivitymeter)
library(ggplot2)
library(knitr)

set.seed(38)

# standard van Westendorp Price Sensitivity Meter Analysis
# input directly via vectors

tch <- round(rnorm(n = 500, mean = 25, sd = 5), digits = 1)
ch <- round(rnorm(n = 500, mean = 35, sd = 10), digits = 1)
ex <- round(rnorm(n = 500, mean = 45, sd = 10), digits = 1)
tex <- round(rnorm(n = 500, mean = 60, sd = 5), digits = 1)

output_psm_demo1 <- psm_analysis(toocheap = tch,
  cheap = ch,
  expensive = ex,
  tooexpensive = tex)

# additional analysis with Newton Miller Smith Extension
# input via data.frame

pint_ch <- sample(x = c(1:5), size = length(tex),
  replace = TRUE, prob = c(0.1, 0.1, 0.2, 0.3, 0.3))

pint_ex <- sample(x = c(1:5), size = length(tex),
  replace = TRUE, prob = c(0.3, 0.3, 0.2, 0.1, 0.1))

data_psm_demo <- data.frame(tch, ch, ex, tex, pint_ch, pint_ex)

output_psm_demo2 <- psm_analysis(toocheap = "tch",
  cheap = "ch",
  expensive = "ex",
  tooexpensive = "tex",
  pi_cheap = "pint_ch",
  pi_expensive = "pint_ex",
  data = data_psm_demo,
  intersection_method = "min",
  interpolation_steps=.01)

# Exctract the cdf functions for plotting
figureoutput <- output_psm_demo2$data_vanwestendorp


# Hack package function to extract intersection points of too cheap/expensive and too expensive/cheap
identify_intersection <- function(data, var1, var2, method) {
  first_intersection_pos <- which(data[, var1] >= data[, var2])[1]

  if(is.na(first_intersection_pos)) { # if no intersection: return NA
    return(NA)
  } else { # otherwise, run the actual function
  all_intersections_pos <- which(data[, var1] == data[first_intersection_pos, var1] &
                                   data[, var2] == data[first_intersection_pos, var2])

  all_intersections_prices <- data[all_intersections_pos, "price"]

  switch(method,
         min = {min(all_intersections_prices)},
         max = {max(all_intersections_prices)},
         mean = {mean(all_intersections_prices)},
         median = {median(all_intersections_prices)})
  }
}

pricerange_lower2 <- identify_intersection(data = figureoutput,
                                            var1 = "ecdf_expensive",
                                            var2 = "ecdf_toocheap",
                                            method = "min")


identify_intersection2 <- function(data, var1, var2, method) {
  first_intersection_pos <- which(data[, var1] <= data[, var2])[1]

  if(is.na(first_intersection_pos)) { # if no intersection: return NA
    return(NA)
  } else { # otherwise, run the actual function
  all_intersections_pos <- which(data[, var1] == data[first_intersection_pos, var1] &
                                   data[, var2] == data[first_intersection_pos, var2])

  all_intersections_prices <- data[all_intersections_pos, "price"]

  switch(method,
         min = {min(all_intersections_prices)},
         max = {max(all_intersections_prices)},
         mean = {mean(all_intersections_prices)},
         median = {median(all_intersections_prices)})
  }
}
pricerange_upper2 <- identify_intersection2(data = figureoutput,
                                            var1 = "ecdf_cheap",
                                            var2 = "ecdf_tooexpensive",
                                            method = "min")

optimalpp <- output_psm_demo2$opp
indifferencepp <- output_psm_demo2$idp

# Build plot using ggplot with the cdfs and the intersection points
psm_plot <- ggplot(figureoutput, aes(x=price, y=ecdf_toocheap, color="Too Cheap")) +
            geom_line() +
            geom_line(aes(x=price, y=ecdf_cheap, color="Cheap")) +
            geom_line(aes(x=price, y=ecdf_expensive, color="Expensive")) +
            geom_line(aes(x=price, y=ecdf_tooexpensive, color="Too Expensive")) +
            ylab("Cumulative Distribution") +
            geom_vline(xintercept=output_psm_demo2$opp, line=1, linetype="dashed") +
            geom_vline(xintercept=output_psm_demo2$idp, line=1, linetype="dotdash") +
            geom_vline(xintercept=pricerange_lower2, line=1, linetype="dotted") +
            geom_vline(xintercept=pricerange_upper2, line=1, linetype="dotted") +
            theme_minimal() + theme(panel.grid.minor = element_blank(), panel.grid.major = element_blank()) +
            ggtitle("Van Westendrop Price Sensitivity Example") +
            scale_color_manual(name = "", values = c("Too Cheap" = "navyblue", "Cheap" = "blue", 
                                                            "Expensive" = "green", "Too Expensive"="darkgreen"))

Sample Survey Data

For this example, the 500 survey responses are randomly generated. To get a sense for the data, the below table shows the survey responses across the four key variables: tch, ch, ex, tex. Note, given this data was randomly generated from a distribution, like any analysis, nonsensical responses are automatically dropped from inclusion (i.e. tch < ch < ex < tex).

library(kableExtra)
library(knitr)

displaydata <- data_psm_demo[,1:4]

kable(head(displaydata, n=10)) %>%
  kable_styling(bootstrap_options = "striped", full_width = F)
tch ch ex tex
23.7 52.5 53.9 63.8
19.7 37.6 48.8 55.3
28.4 38.7 53.4 64.1
25.1 38.4 39.0 58.3
16.6 48.0 62.2 64.2
17.6 47.4 39.6 61.1
27.2 35.9 36.5 64.4
25.0 15.0 34.0 59.0
29.4 43.7 53.6 64.5
23.0 23.5 41.9 64.9


Cumulative Distribution Calculation

Using the survey responses, each variable is rank ordered from smallest to largest and a cumulative distribution is calculated for all responses. The cumulative distribution for each variable is then plotted against the price points. The below data snapshot demonstrates this idea.


library(kableExtra)
library(knitr)

displaydata2 <- figureoutput[,1:5]

kable(head(displaydata2, n=15)) %>%
  kable_styling(bootstrap_options = "striped", full_width = F)
price ecdf_toocheap ecdf_cheap ecdf_expensive ecdf_tooexpensive
12.3 0.9961240 1.0000000 0 0
13.0 0.9922481 1.0000000 0 0
13.5 0.9883721 1.0000000 0 0
14.1 0.9844961 1.0000000 0 0
14.5 0.9844961 0.9961240 0 0
14.6 0.9806202 0.9961240 0 0
14.9 0.9767442 0.9961240 0 0
15.0 0.9728682 0.9961240 0 0
15.1 0.9689922 0.9961240 0 0
15.6 0.9651163 0.9961240 0 0
16.0 0.9651163 0.9922481 0 0
16.2 0.9573643 0.9922481 0 0
16.3 0.9534884 0.9922481 0 0
16.4 0.9496124 0.9922481 0 0
16.5 0.9457364 0.9922481 0 0


Graphical Analysis & Interpretation

A quick look at the below figure helps to clarify the value of this analysis. In its simplest form, there four key points of note as stated by Allestee in the r pricesensitivitymeter documentation:

  1. Point of Marginal Cheapness (MGP): Below this price point, there are more respondents that consider the price as “too cheap” than respondents who consider it as “expensive” (intersection of “too cheap” and “expensive”). This is interpreted as the lower limit of the range of acceptable prices.

  2. Point of Marginal Expensiveness (MEP). Above this price point, there are more respondent that consider the price as “too expensive” than there are respondents who consider it as “cheap” (intersection of “cheap” and “too expensive”). This is interpreted as the upper limit of the range of acceptable prices.

  3. Indifference Price Point (IDP): The same number of respondents perceives the price as “cheap” and “expensive” (intersection of “cheap” and “expensive”). In van Westendorp’s interpretation, this is either the median price paid in the market or the price of an important market-leader.

  4. Optimal Price Point (OPP): The same number of respondents perceives the product as “too cheap” and “too expensive” (intersection of “too cheap” and “too expensive”). van Westendorp argues that this is the value for which the respondents’ resistance against the price is particularly low.

psm_plot




Van Westendorp Price Sensitivity Meter Analysis

Based on this analysis, this test product should be priced between $33.8-$51.3, has an optimal price point of $37.7, and an indifference price point of $41.8.