DEEPPRICE Procedure
Example 16.1 Personalized Customer Segmentation for an Online Media Company
This example illustrates how a music subscription service from an online media company can offer targeted discounts through a personalized pricing plan based on many features that the company observes about its customers to encourage them to buy more songs.
The data set is provided by the Microsoft research project ALICE and is available at https://econmldata.azurewebsites.net/datasets/Pricing/pricing_sample.csv. The data set has 10,000 simulated observations that represent users’ personal characteristics, such as age, log income, and account age, as well as users’ online behavior history, including previous purchases and previous online time per week. The treatment variable, price, is the price that the customer was exposed to during the discount season. A value of 1 indicates that no discount is given, and a value less than 1 indicates that a discount is applied. The outcome variable, demand, is the number of songs that the customer purchased during the discount season. Table 3 shows the names of the variables that are used in the model, their types, and their definitions. The type can be T (treatment), Y (outcome), (a covariate in the treatment model), and/or
(a covariate in the outcome model).
Table 3: Model Variables
This example has three goals:
Estimate the parameters of interest, such as average partial effect of price on demand.
Estimate the price elasticity of demand for songs, and assess its variation with users’ characteristics.
Conduct a policy analysis that aims at increasing revenue despite decreasing the price for some users.
Assuming that you have downloaded the data set pricing_sample in your session that is associated with the mylib libref, the following statements create an ID variable that has a unique value for each observation in the data table new_pricing_sample:
data mylib.new_pricing_sample;
set mylib.pricing_sample;
id = put(_threadid_,8.) || '_' || Put(_n_,8.); * ID variable;
run;
The first step in policy evaluation and policy optimization is to estimate the effect of the treatment and to save to a specified output data table the details of the estimation, including and
, the residual, and the influence functions for each unit. You can do this by using the following statements, which also store the estimated treatment model information,
information, and
information in the data tables
outtdata, outadata, and outbdata, respectively, to be used later for policy evaluation:
/*--- Estimate the treatment effect and save the estimation details ---*/
proc deepprice data=mylib.new_pricing_sample;
id id;
tmodel price = account_age age avg_hours days_visited friends_count
has_membership is_US songs_purchased income /
dnn=(nodes=(32 32 32 32)
train=(optimizer=(miniBatchSize=500 regL1=0.0001 maxEpochs=32000
algorithm=adam) nthreads=20 seed=12345 recordseed=67890));
model demand = account_age age avg_hours days_visited friends_count
has_membership is_US songs_purchased income /
dnn=(nodes=(32 32 32 32)
train=(optimizer=(miniBatchSize=500 regL1=0.001 maxEpochs=32000
algorithm=adam) nthreads=20 seed=12345 recordseed=67890));
infer out=mylib.oest outdetails=mylib.odetails;
score outt=mylib.outtdata outa=mylib.outadata outb=mylib.outbdata;
run;
For this example, the same covariates are used in both the treatment model and the outcome model. The model estimation details are saved in the output data table odetails.
The estimation results are shown in Output 16.1.1. The estimate of the average partial effect of price on demand is negative, as expected, and statistically significant.
Output 16.1.1: Parameters of Interest
| Parameter Estimates | ||||||
|---|---|---|---|---|---|---|
| Parameter | Estimate | Standard Error | 95% Confidence Limits | Z | Pr > |Z| | |
| Average Intercept | 23.629698 | 0.215064 | 23.208180 | 24.051217 | 109.87 | <.0001 |
| Average Slope | -9.235895 | 0.244447 | -9.715001 | -8.756788 | -37.78 | <.0001 |
Another parameter of interest is the price elasticity of demand, which measures the degree of sensitivity of demand to price. In general, demand decreases when price increases for most products, but the amount by which demand decreases is greater for some products than for others. Price elasticity measures the percentage change in demand when there is a 1% increase in price. In this example, a price elasticity of –3 means that a 1% increase in price will lead to a 3% decrease in the number of songs purchased.
Heterogeneity in price sensitivity is introduced among users by allowing the price elasticity to depend on users’ characteristics , so that the heterogeneous elasticity function is defined for the ith user as
It is of particular interest to assess how price elasticity varies among users with respect to their income and other personal characteristics. The variable _beta_ in the data table odetails that is obtained during the previous estimation contains the estimated values for this function for each user. Price elasticity is computed for each user by using the following statements:
data odetails;
set mylib.odetails;
elasticity = _beta_ *price/demand;
run;
The estimated price elasticities can then be plotted against various characteristics of users. The following statements plot the relationship between elasticity and income, which is shown in Output 16.1.2:
proc sort data=odetails ;
by income;
run;
proc sgplot data=odetails ;
series x=income y=elasticity /legendlabel='Sales elasticity prediction';
xaxis label='Income' values=(0 to 6 by 1);
yaxis label='Song sales elasticity';
run;
Output 16.1.2: Song Sales Price Elasticity vs. Income

Output 16.1.2 shows a remarkable heterogeneity among users with respect to income for their response to a price increase. Users whose income is less than 1 (51% of all users) are more sensitive to a price increase; if price goes up by 1%, their number of songs purchased falls by 1% to 5%, compared to around 0.15% for higher-income users.
The following statements plot the relationship between elasticity, income, and days_visited, which is shown in Output 16.1.3:
proc sort data=odetails ;
by days_visited income;
run;
proc sgpanel data=odetails;
panelby days_visited/layout=panel columns=4 rows=2 uniscale=column;
series x=income y=elasticity;
colaxis label='Income' valueshint values=(0 1 2 3 4 5 6);
rowaxis label='Song sales elasticity' grid;
run;
Output 16.1.3: Song Sales Price Elasticity vs. Income and Days Visited

Output 16.1.3 further shows that low-income users who visited the website less often in the past (less than five days a week) are even more price-sensitive than low-income users who visited the website more often. The discussion next turns to policy evaluation and comparison.
In policy optimization, the goal is to maximize the expected utility of a policy. For the definition of the expected utility function and how it is estimated, as well as details such as the definition of a policy rule, see the section Policy Evaluation and Comparison. In this example, the policy decisions are to choose user-specific prices () in order to maximize the revenue, which is defined as
The optimal price for each user is therefore derived as . Let
s1 be the optimal policy that sets each user’s price as . This optimal policy is highly personalized, because it sets user-specific prices during the discount season, but those prices can exceed the original prices. It is therefore more meaningful to consider an optimized discount personalized policy,
s1d, that is equal to s1 if s1 does not exceed the original price, and equal to 1 otherwise.
Although the optimal pricing policy s1 guarantees the maximum revenue and is highly personalized, the online media company might want to consider a price discount that targets a group of users or all users. Whether the company’s revenue will rise or fall depends on the price elasticity of songs’ demand. Microeconomic theory predicts that lowering prices will increase revenue if demand is price-elastic (elasticity < –1) and decrease revenue if demand is price-inelastic (elasticity > –1).
Output 16.1.2 and Output 16.1.3 show that price elasticity is heterogeneous among users; the most price-sensitive users are those whose income is less than 1 and who spend on average less than five days a week on the website. This suggests a policy that offers this type of user a price discount. Let s2 be this policy. For comparison, also consider policies s3 (offer everyone a discount) and s0 (offer no one a discount).
To select the discount rates, revenues were computed for policies s2 and s3 and for each of the six discount values 5%, 10%, 15%, 20%, 25%, and 30%. For policy s2, a 5% discount did not improve the base policy price, and although a 10%, 15%, 20%, 25%, or 30% discount generated more revenue than the base policy, there was no significant difference among the five discount rates. A 10% discount was therefore selected for policy s2. Using a similar analysis, a 5% discount was selected for policy s3.
Policies s2 and s3 offer the same discount to all users or groups of users. Versions of these policies (s4 and s5) that randomly select with equal probability a discount rate from the set (5%, 10%, 15%, 20%, 25%, and 30%) are also considered.
The following DATA step creates policies s0, s1, s1d, s2, s3, s4, and s5 in the data table odetails, which also contains the variable elasticity that was previously computed:
data mylib.discountPolicy;
array prob[6] _temporary_ (6*0.1666667);
call streaminit(54321);
set odetails;
s0 = 1;
s1 = -_alpha_/(2*_beta_);
if s1 >1 then s1d = 1;
else s1d = s1;
if (income < 1 and days_visited <5) then s2 = 0.9;
else s2 = 1;
s3 = 0.95;
if (income < 1 and days_visited <5) then s4c = rand("Table", of prob[*]);
else s4c = 0;
s4=.;
do k = 1 to 7;
if (s4c = k-1) then s4 = 1 - 0.05*(k-1);
end;
s5c = rand("Table", of prob[*]);
s5=.;
do k = 1 to 7;
if (s5c = k-1) then s5 = 1 - 0.05*(k-1);
end;
keep id price account_age age avg_hours days_visited
friends_count has_membership is_US songs_purchased
income demand s0 s1 s1d s2 s3 s4 s5 elasticity;
run;
Before policy evaluation and comparison are conducted, the variation of the optimal prices with respect to users’ price sensitivity and income is assessed.
In the following code, the DATA step defines the variables elasticity_type and price_discount, and the PROC steps plot the relationship between price_discount, elasticity_type, and income, which
is shown in Output 16.1.4:
data discountPolicy2;
set mylib.discountPolicy;
if elasticity >-1 then elasticity_type = 'Inelastic';
else if elasticity <-1 then elasticity_type = 'Elastic';
else if elasticity =-1 then elasticity_type = 'Unit Elastic';
price_discount = ((1-s1)/1)*100;
run;
proc sort data=discountPolicy2 ;
by elasticity_type income;
run;
proc sgpanel data=discountPolicy2;
panelby elasticity_type / layout=panel columns=2 rows=1 uniscale=column;
series x=income y=price_discount;
colaxis label='Income' valueshint values=(0 1 2 3 4 5 6);
rowaxis label='Price discount (%)' grid max=200 min=-1000;
run;
Output 16.1.4: Price Discount Distribution by Elasticity and Income

Output 16.1.4 shows that the optimal policy offers a positive discount to users whose income is less than 1 and to nearly all price-sensitive users (elastic group).
You can do policy evaluation by specifying the POLICY= option in the INFER statement, and you can do policy comparison by specifying the POLICYCOMPARISON= option in the INFER statement. The previously estimated treatment model, information, and
information are applied to the new data set by specifying the INT=, INA=, and INB= options, respectively, in the SCORE statement. The following statements estimate the model and do policy evaluation and comparison:
/*--- Policy evaluation and comparison ---*/
proc deepprice data=mylib.discountPolicy;
id id;
infer out=mylib.oest2 outdetails=mylib.odetails2
policy=(price s0 s1 s1d s2 s3 s4 s5)
policyComparison=(base=(price)
compare=(s0 s1 s1d s2 s3 s4 s5));
score int=mylib.outtdata ina=mylib.outadata inb=mylib.outbdata ;
run;
The POLICY= option performs the policy evaluation for the initial price policy (price) and policies s0, s1, s1d, s2, s3, s4, and s5. The COMPARE= suboption of the POLICYCOMPARISON= option compares policies s0, s1, s1d, s2, s3, s4, and s5 to the base policy, price, which is specified by the BASE= sub
option of the POLICYCOMPARISON= option.
The policy evaluation results are shown in Output 16.1.5.
Output 16.1.5: Policy Evaluation
| Policy Evaluation | ||||||
|---|---|---|---|---|---|---|
| Policy | Estimate | Standard Error | 95% Confidence Limits | Z | Pr > |Z| | |
| price | 14.648911 | 0.073155 | 14.505529 | 14.792293 | 200.24 | <.0001 |
| s0 | 14.393804 | 0.077702 | 14.241511 | 14.546096 | 185.24 | <.0001 |
| s1 | 12.017483 | 0.659866 | 10.724169 | 13.310798 | 18.21 | <.0001 |
| s1d | 16.271219 | 0.059482 | 16.154637 | 16.387800 | 273.55 | <.0001 |
| s2 | 14.869897 | 0.069841 | 14.733011 | 15.006784 | 212.91 | <.0001 |
| s3 | 14.855598 | 0.072180 | 14.714128 | 14.997069 | 205.81 | <.0001 |
| s4 | 15.220208 | 0.069391 | 15.084205 | 15.356211 | 219.34 | <.0001 |
| s5 | 15.985290 | 0.070627 | 15.846863 | 16.123717 | 226.33 | <.0001 |
The optimal policy s1 has the lowest expected demand, whereas the optimized discount policy s1d and the policy s5, which offer each user a randomly selected discount, generate the most demand. Policies s2, s3, and s4 generate slightly more demand than s0 (no user gets a discount).
Output 16.1.6 shows the output for the policy comparison. Policies s1d and s5 are better than the current price policy with respect to demand.
Output 16.1.6: Policy Comparison
| Policy Comparison | |||||||
|---|---|---|---|---|---|---|---|
| Comparison Policy | Base Policy | Estimate | Standard Error | 95% Confidence Limits | Z | Pr > |Z| | |
| s0 | price | -0.255108 | 0.035676 | -0.325032 | -0.185183 | -7.15 | <.0001 |
| s1 | price | -2.631428 | 0.671764 | -3.948062 | -1.314794 | -3.92 | <.0001 |
| s1d | price | 1.622307 | 0.041549 | 1.540872 | 1.703742 | 39.05 | <.0001 |
| s2 | price | 0.220986 | 0.029949 | 0.162287 | 0.279685 | 7.38 | <.0001 |
| s3 | price | 0.206687 | 0.027870 | 0.152063 | 0.261311 | 7.42 | <.0001 |
| s4 | price | 0.571296 | 0.037946 | 0.496924 | 0.645669 | 15.06 | <.0001 |
| s5 | price | 1.336379 | 0.035783 | 1.266245 | 1.406512 | 37.35 | <.0001 |
Output 16.1.5 and Output 16.1.6 evaluate and compare the different policies on the basis of demand. However, it would be better for the comparison of the policies to be based on revenue.
The following statements evaluate and compare the different policies by computing the average revenue per user and the associated confidence interval for each policy in the data table odetails2:
data odetails2;
set mylib.odetails2;
if (_alpha_+_beta_*price>0) then revenue_price = price*(_alpha_+_beta_*price);
else revenue_price = 0;
if (_alpha_+_beta_*s0>0) then revenue_s0 = s0*(_alpha_+_beta_*s0);
else revenue_s0 = 0;
if (_alpha_+_beta_*s1>0 and s1>0) then revenue_s1 = s1*(_alpha_+_beta_*s1);
else revenue_s1 = 0;
if (_alpha_+_beta_*s1d>0 and s1d>0) then revenue_s1d = s1d*(_alpha_+_beta_*s1d);
else revenue_s1d = 0;
if (_alpha_+_beta_*s2>0) then revenue_s2 = s2*(_alpha_+_beta_*s2);
else revenue_s2 = 0;
if (_alpha_+_beta_*s3>0) then revenue_s3 = s3*(_alpha_+_beta_*s3);
else revenue_s3 = 0;
if (_alpha_+_beta_*s4>0) then revenue_s4 = s4*(_alpha_+_beta_*s4);
else revenue_s4 = 0;
if (_alpha_+_beta_*s5>0) then revenue_s5 = s5*(_alpha_+_beta_*s5);
else revenue_s5 = 0;
run;
proc means data=odetails2 noprint;
var revenue_price revenue_s0 revenue_s1 revenue_s1d revenue_s2 revenue_s3
revenue_s4 revenue_s5;
output out=odetails2_stats(drop=_type_ _freq_) mean= STD= N=/autoname;
run;
data odetails2_stats;
set odetails2_stats;
s0_price_Estimate = revenue_s0_mean - revenue_price_mean;
s1_price_Estimate = revenue_s1_mean - revenue_price_mean;
s1d_price_Estimate = revenue_s1d_mean - revenue_price_mean;
s2_price_Estimate = revenue_s2_mean - revenue_price_mean;
s3_price_Estimate = revenue_s3_mean - revenue_price_mean;
s4_price_Estimate = revenue_s4_mean - revenue_price_mean;
s5_price_Estimate = revenue_s5_mean - revenue_price_mean;
revenue_price_StdErr = revenue_price_StdDev/sqrt(revenue_price_N);
revenue_s0_StdErr = revenue_s0_StdDev/sqrt(revenue_s0_N);
revenue_s1_StdErr = revenue_s1_StdDev/sqrt(revenue_s1_N);
revenue_s1d_StdErr = revenue_s1d_StdDev/sqrt(revenue_s1d_N);
revenue_s2_StdErr = revenue_s2_StdDev/sqrt(revenue_s2_N);
revenue_s3_StdErr = revenue_s3_StdDev/sqrt(revenue_s3_N);
revenue_s4_StdErr = revenue_s4_StdDev/sqrt(revenue_s4_N);
revenue_s5_StdErr = revenue_s5_StdDev/sqrt(revenue_s5_N);
s0_price_StdErr = sqrt(revenue_s0_StdErr**2 + revenue_price_StdErr**2);
s1_price_StdErr = sqrt(revenue_s1_StdErr**2 + revenue_price_StdErr**2);
s1d_price_StdErr = sqrt(revenue_s1d_StdErr**2 + revenue_price_StdErr**2);
s2_price_StdErr = sqrt(revenue_s2_StdErr**2 + revenue_price_StdErr**2);
s3_price_StdErr = sqrt(revenue_s3_StdErr**2 + revenue_price_StdErr**2);
s4_price_StdErr = sqrt(revenue_s4_StdErr**2 + revenue_price_StdErr**2);
s5_price_StdErr = sqrt(revenue_s5_StdErr**2 + revenue_price_StdErr**2);
run;
proc transpose data=odetails2_stats(drop=revenue_price_N revenue_s1_N
revenue_s1d_N revenue_s2_N revenue_s3_N revenue_s4_N revenue_s5_N
revenue_s0_N revenue_price_StdDev revenue_s1_StdDev revenue_s1d_StdDev
revenue_s2_StdDev revenue_s3_StdDev revenue_s4_StdDev revenue_s5_StdDev
revenue_s0_StdDev) OUT=odetails2_stats;
run;
data odetails2_stats2;
set odetails2_stats;
ComparisonPolicy=scan(_name_,1,'_');
BasePolicy=scan(_name_,2,'_');
stat=scan(_name_,3,'_');
if ComparisonPolicy = 'revenue' then Policy = BasePolicy;
drop _name_;
run;
proc sort data=odetails2_stats2;
by ComparisonPolicy BasePolicy Policy;
run;
proc transpose data=odetails2_stats2 out=odetails2_stats2(drop=_name_);
by ComparisonPolicy BasePolicy Policy;
id stat;
var col1;
run;
data odetails2_stats2;
format ProbZ pvalue6.4;
set odetails2_stats2;
if Estimate =. then Estimate=Mean;
Z = Estimate/StdErr;
LowerCL = Estimate - StdErr*quantile('NORMAL',.975);
UpperCL = Estimate + StdErr*quantile('NORMAL',.975);
ProbZ = 2*(1-cdf('NORMAL',abs(Z)));
run;
The table that shows policy evaluation based on revenue is printed by the following statements and shown in Output 16.1.7. As expected, the optimal personalized policy s1 generates the most revenue. The optimized personalized discount policy s1d and the personalized policies s2 and s4 generate comparable revenue, which is also the second-highest revenue.
proc print data=odetails2_stats2 noobs;
var Policy Estimate StdErr LowerCL UpperCL ProbZ;
where Policy ~='';
run;
Output 16.1.7: Policy Evaluation Based on Revenue
| Policy | Estimate | StdErr | LowerCL | UpperCL | ProbZ |
|---|---|---|---|---|---|
| price | 15.0378 | 0.06639 | 14.9077 | 15.1679 | <.0001 |
| s0 | 15.1765 | 0.07319 | 15.0331 | 15.3200 | <.0001 |
| s1 | 34.4007 | 0.33318 | 33.7477 | 35.0537 | <.0001 |
| s1d | 15.6764 | 0.06750 | 15.5441 | 15.8087 | <.0001 |
| s2 | 15.4018 | 0.07024 | 15.2641 | 15.5395 | <.0001 |
| s3 | 14.8507 | 0.06671 | 14.7199 | 14.9814 | <.0001 |
| s4 | 15.4653 | 0.06937 | 15.3294 | 15.6013 | <.0001 |
| s5 | 13.7614 | 0.05384 | 13.6558 | 13.8669 | <.0001 |
Output 16.1.8 shows the output for the policy comparison based on revenue. The personalized policies s1, s1d, s2, and s4 generate more average revenue per user than the base policy price. The policy s0 (offer no user a discount) is equivalent to price, and policies that offer every user a discount (s3 and s5) are worse
than price, with s5 being the worst. The following statements print the policy comparison results, as shown in Output 16.1.8:
proc print data=odetails2_stats2 noobs;
var ComparisonPolicy BasePolicy Estimate StdErr LowerCL UpperCL ProbZ;
where Policy ='';
run;
Output 16.1.8: Policy Comparison Based on Revenue
| ComparisonPolicy | BasePolicy | Estimate | StdErr | LowerCL | UpperCL | ProbZ |
|---|---|---|---|---|---|---|
| s0 | price | 0.1387 | 0.09881 | -0.0549 | 0.3324 | 0.1603 |
| s1 | price | 19.3629 | 0.33973 | 18.6970 | 20.0287 | <.0001 |
| s1d | price | 0.6385 | 0.09467 | 0.4530 | 0.8241 | <.0001 |
| s2 | price | 0.3640 | 0.09665 | 0.1745 | 0.5534 | 0.0002 |
| s3 | price | -0.1872 | 0.09411 | -0.3716 | -0.0027 | 0.0467 |
| s4 | price | 0.4275 | 0.09602 | 0.2393 | 0.6157 | <.0001 |
| s5 | price | -1.2764 | 0.08548 | -1.4440 | -1.1089 | <.0001 |
In fact, the data generating process (DGP) behind the observed demand in the pricing_sample data set is known, and the objectives of this final section are to assess how well the estimated intercept and slope functions approximate the truth and to compare the different polices by using true DGP revenues.
The true DGP is defined as follows:
The following statements compute the true intercept and slope functions, as well as the revenue that is generated per user by the different price policies and by the true DGP demand. The optimal policies s1star and s1dstar are the true DGP counterparts of the estimated policies s1 and s1d. The s1star policy is the best revenue-maximizing policy in theory, but it is not available in practice because it depends on the unknown functions and
.
data odetails2_DGP;
set mylib.odetails2;
alpha_dgp = 20 + 0.5*avg_hours + 5*(days_visited > 4);
beta_dgp = -3 -14*(income < 1);
s1star = -alpha_dgp/(2*beta_dgp);
if s1star>1 then s1dstar = 1; else s1dstar = s1star;
if (alpha_dgp+beta_dgp*price>0) then
revenue_price = price*(alpha_dgp+beta_dgp*price);
else revenue_price =0;
if (alpha_dgp+beta_dgp*s0>0) then
revenue_s0 = s0*(alpha_dgp+beta_dgp*s0);
else revenue_s0 =0;
if (alpha_dgp+beta_dgp*s1>0 and s1>0) then
revenue_s1 = s1*(alpha_dgp+beta_dgp*s1);
else revenue_s1 = 0;
if (alpha_dgp+beta_dgp*s1d>0 and s1d>0) then
revenue_s1d = s1d*(alpha_dgp+beta_dgp*s1d);
else revenue_s1d = 0;
if (alpha_dgp+beta_dgp*s1star>0 and s1star>0) then
revenue_s1star = s1star*(alpha_dgp+beta_dgp*s1star);
else revenue_s1star = 0;
if (alpha_dgp+beta_dgp*s1dstar>0 and s1dstar>0) then
revenue_s1dstar = s1dstar*(alpha_dgp+beta_dgp*s1dstar);
else revenue_s1dstar = 0;
if (alpha_dgp+beta_dgp*s2>0) then revenue_s2 = s2*(alpha_dgp+beta_dgp*s2);
else revenue_s2 = 0;
if (alpha_dgp+beta_dgp*s3>0) then revenue_s3 = s3*(alpha_dgp+beta_dgp*s3);
else revenue_s3 = 0;
if (alpha_dgp+beta_dgp*s4>0) then revenue_s4 = s4*(alpha_dgp+beta_dgp*s4);
else revenue_s4 = 0;
if (alpha_dgp+beta_dgp*s5>0) then revenue_s5 = s5*(alpha_dgp+beta_dgp*s5);
else revenue_s5 = 0;
run;
The true and estimated intercept functions are then plotted using the code that follows. The results are shown in Output 16.1.9. The estimated and true intercepts are highly positively correlated, and the estimated slope function reasonably approximates the true slope except in the case of high-income users. It should be noted that the estimated intercept and slope are functions of
all nine users’ characteristics, whereas the true intercept and slope are functions of only income, avg_hours, and days_visited. Despite this difference, the DEEPPRICE procedure is able to accurately estimate the patter
ns in these functions.
proc sort data=odetails2_DGP ;
by alpha_dgp;
run;
proc sgplot data=odetails2_DGP noautolegend;
lineparm x=0 y=0 slope=1/lineattrs=(color=red) ;
series x=_alpha_ y=alpha_dgp ;
xaxis min=15 max=35;
yaxis min=15 max=35;
run;
The following statements plot the true and estimated slope functions, as shown in Output 16.1.10:
proc sort data=odetails2_DGP ;
by income;
run;
proc sgplot data=odetails2_DGP ;
series x=income y=beta_dgp /legendlabel='True slope';
series x=income y=_beta_ /legendlabel='Estimated slope';
xaxis label='Income' values=(0 to 6 by 1);
yaxis label='Slope';
run;
As with the non-DGP revenues, the following statements compute the average revenue per user for each price policy in the data table odetails2_DGP:
proc means data=odetails2_DGP noprint;
var revenue_price revenue_s0 revenue_s1 revenue_s1star revenue_s1d
revenue_s1dstar revenue_s2 revenue_s3 revenue_s4 revenue_s5;
output out=odetails2_DGP_stats(drop=_type_ _freq_) mean= STD= N=/autoname;
run;
data odetails2_DGP_stats;
set odetails2_DGP_stats;
s0_price_Estimate = revenue_s0_mean - revenue_price_mean;
s1_price_Estimate = revenue_s1_mean - revenue_price_mean;
s1d_price_Estimate = revenue_s1d_mean - revenue_price_mean;
s1star_price_Estimate = revenue_s1star_mean - revenue_price_mean;
s1dstar_price_Estimate = revenue_s1dstar_mean - revenue_price_mean;
s2_price_Estimate = revenue_s2_mean - revenue_price_mean;
s3_price_Estimate = revenue_s3_mean - revenue_price_mean;
s4_price_Estimate = revenue_s4_mean - revenue_price_mean;
s5_price_Estimate = revenue_s5_mean - revenue_price_mean;
revenue_s0_StdErr = revenue_s0_StdDev/sqrt(revenue_s0_N);
revenue_price_StdErr = revenue_price_StdDev/sqrt(revenue_price_N);
revenue_s1_StdErr = revenue_s1_StdDev/sqrt(revenue_s1_N);
revenue_s1d_StdErr = revenue_s1d_StdDev/sqrt(revenue_s1d_N);
revenue_s1star_StdErr = revenue_s1star_StdDev/sqrt(revenue_s1star_N);
revenue_s1dstar_StdErr = revenue_s1dstar_StdDev/sqrt(revenue_s1dstar_N);
revenue_s2_StdErr = revenue_s2_StdDev/sqrt(revenue_s2_N);
revenue_s3_StdErr = revenue_s3_StdDev/sqrt(revenue_s3_N);
revenue_s4_StdErr = revenue_s4_StdDev/sqrt(revenue_s4_N);
revenue_s5_StdErr = revenue_s5_StdDev/sqrt(revenue_s5_N);
s0_price_StdErr = sqrt(revenue_s0_StdErr**2 + revenue_price_StdErr**2);
s1_price_StdErr = sqrt(revenue_s1_StdErr**2 + revenue_price_StdErr**2);
s1d_price_StdErr = sqrt(revenue_s1d_StdErr**2 + revenue_price_StdErr**2);
s1star_price_StdErr = sqrt(revenue_s1star_StdErr**2 + revenue_price_StdErr**2);
s1dstar_price_StdErr = sqrt(revenue_s1dstar_StdErr**2 + revenue_price_StdErr**2);
s2_price_StdErr = sqrt(revenue_s2_StdErr**2 + revenue_price_StdErr**2);
s3_price_StdErr = sqrt(revenue_s3_StdErr**2 + revenue_price_StdErr**2);
s4_price_StdErr = sqrt(revenue_s4_StdErr**2 + revenue_price_StdErr**2);
s5_price_StdErr = sqrt(revenue_s5_StdErr**2 + revenue_price_StdErr**2);
run;
proc transpose data=odetails2_DGP_stats(drop= revenue_price_N revenue_s0_N
revenue_s1_N revenue_s1d_N revenue_s1star_N revenue_s1dstar_N
revenue_s2_N revenue_s3_N revenue_s4_N revenue_s5_N revenue_price_StdDev
revenue_s0_StdDev revenue_s1_StdDev revenue_s1d_StdDev revenue_s1star_StdDev
revenue_s1dstar_StdDev revenue_s2_StdDev revenue_s3_StdDev revenue_s4_StdDev
revenue_s5_StdDev) OUT=odetails2_DGP_stats;
run;
data odetails2_DGP_stats2;
set odetails2_DGP_stats;
ComparisonPolicy=scan(_name_,1,'_');
BasePolicy=scan(_name_,2,'_');
stat=scan(_name_,3,'_');
if ComparisonPolicy = 'revenue' then Policy = BasePolicy;
drop _name_;
run;
proc sort data=odetails2_DGP_stats2;
by ComparisonPolicy BasePolicy Policy;
run;
proc transpose data=odetails2_DGP_stats2
out=odetails2_DGP_stats2(drop=_name_);
by ComparisonPolicy BasePolicy Policy;
id stat;
var col1;
run;
data odetails2_DGP_stats2;
format ProbZ pvalue6.4;
set odetails2_DGP_stats2;
if Estimate =. then Estimate=Mean;
Z = Estimate/StdErr;
LowerCL = Estimate - StdErr*quantile('NORMAL',.975);
UpperCL = Estimate + StdErr*quantile('NORMAL',.975);
ProbZ = 2*(1-cdf('NORMAL',abs(Z)));
run;
The true DGP average revenue per user and the corresponding confidence interval for each price policy are printed using the following code and shown in Output 16.1.11 and Output 16.1.12, respectively:
data odetails2_DGP_stats2_eval;
set odetails2_DGP_stats2(where=(Policy ~=''));
order = _n_;
if Policy = 's1star' then order = 4;
if Policy = 's1d' then order = 5;
if Policy = 's1dstar' then order = 6;
run;
proc sort data=odetails2_DGP_stats2_eval;
by order;
run;
proc print data=odetails2_DGP_stats2_eval noobs;
var Policy Estimate StdErr LowerCL UpperCL ProbZ;
run;
Output 16.1.11: Policy Evaluation Based on True DGP Revenue
| Policy | Estimate | StdErr | LowerCL | UpperCL | ProbZ |
|---|---|---|---|---|---|
| price | 14.3493 | 0.06697 | 14.2181 | 14.4806 | <.0001 |
| s0 | 14.2946 | 0.07480 | 14.1480 | 14.4412 | <.0001 |
| s1 | 25.9909 | 0.20765 | 25.5839 | 26.3979 | <.0001 |
| s1star | 29.2230 | 0.22100 | 28.7898 | 29.6561 | <.0001 |
| s1d | 14.9563 | 0.06759 | 14.8238 | 15.0888 | <.0001 |
| s1dstar | 15.0342 | 0.06684 | 14.9032 | 15.1652 | <.0001 |
| s2 | 14.6058 | 0.07117 | 14.4663 | 14.7453 | <.0001 |
| s3 | 14.0598 | 0.06796 | 13.9266 | 14.1930 | <.0001 |
| s4 | 14.7221 | 0.06988 | 14.5851 | 14.8591 | <.0001 |
| s5 | 13.1714 | 0.05423 | 13.0651 | 13.2777 | <.0001 |
The following statements print the comparison of each policy to price, as shown in Output 16.1.12:
data odetails2_DGP_stats2_comp;
set odetails2_DGP_stats2(where=(Policy =''));
order = _n_;
if ComparisonPolicy = 's1star' then order = 4;
if ComparisonPolicy = 's1d' then order = 5;
if ComparisonPolicy = 's1dstar' then order = 6;
run;
proc sort data=odetails2_DGP_stats2_comp;
by order;
run;
proc print data=odetails2_DGP_stats2_comp noobs;
var ComparisonPolicy BasePolicy Estimate StdErr LowerCL UpperCL ProbZ;
run;
Output 16.1.12: Policy Comparison Based on True DGP Revenue
| ComparisonPolicy | BasePolicy | Estimate | StdErr | LowerCL | UpperCL | ProbZ |
|---|---|---|---|---|---|---|
| s0 | price | -0.0547 | 0.10040 | -0.2515 | 0.1421 | 0.5856 |
| s1 | price | 11.6416 | 0.21818 | 11.2139 | 12.0692 | <.0001 |
| s1star | price | 14.8736 | 0.23093 | 14.4210 | 15.3262 | <.0001 |
| s1d | price | 0.6070 | 0.09515 | 0.4205 | 0.7935 | <.0001 |
| s1dstar | price | 0.6849 | 0.09462 | 0.4994 | 0.8703 | <.0001 |
| s2 | price | 0.2564 | 0.09773 | 0.0649 | 0.4480 | 0.0087 |
| s3 | price | -0.2895 | 0.09542 | -0.4766 | -0.1025 | 0.0024 |
| s4 | price | 0.3728 | 0.09679 | 0.1830 | 0.5625 | 0.0001 |
| s5 | price | -1.1779 | 0.08618 | -1.3468 | -1.0090 | <.0001 |
Output 16.1.11 shows that the average revenue per user that is generated by the estimated optimal policy s1 is less than but close to that obtained using its true DGP counterpart and best policy, s1star. Without knowing the true DGP, the DEEPPRICE procedure can accurately estimate the average optimal revenue per user. The optimized discount policies s1d and s1dstar generate comparable but substantially less revenue than s1 and s1star.
In terms of policy comparison, Output 16.1.12 shows that the s1 and s1star policies generate much more revenue per user than the base policy price. The optimized discount personalized policies (s1d and s1dstar) and the personalized policies that offer only price-sensitive users a discount (s2 and s4) are better than price, whereas the policies that offer every user a discount (s3 and s5) are worse than price.
In summary, this example shows how, by taking advantage of the flexibility of PROC DEEPPRICE in specifying heterogeneous price effects as a function of users’ characteristics, the online media company can set user-specified prices to achieve maximum revenue and discount policies that target a specified group of users.

