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), bold x Superscript left parenthesis 1 right parenthesis (a covariate in the treatment model), and/or bold x Superscript left parenthesis 2 right parenthesis (a covariate in the outcome model).

Table 3: Model Variables

Name Type Details
account_age bold x Superscript left parenthesis 1 right parenthesis, bold x Superscript left parenthesis 2 right parenthesis User’s account age
age bold x Superscript left parenthesis 1 right parenthesis, bold x Superscript left parenthesis 2 right parenthesis User’s age
avg_hours bold x Superscript left parenthesis 1 right parenthesis, bold x Superscript left parenthesis 2 right parenthesis Average number of hours per week user was online in the past
days_visited bold x Superscript left parenthesis 1 right parenthesis, bold x Superscript left parenthesis 2 right parenthesis Average number of days per week user visited website in the past
friend_count bold x Superscript left parenthesis 1 right parenthesis, bold x Superscript left parenthesis 2 right parenthesis Number of friends user connected to in account
has_membership bold x Superscript left parenthesis 1 right parenthesis, bold x Superscript left parenthesis 2 right parenthesis Whether user has membership
is_US bold x Superscript left parenthesis 1 right parenthesis, bold x Superscript left parenthesis 2 right parenthesis Whether user accesses website from the United States
songs_purchased bold x Superscript left parenthesis 1 right parenthesis, bold x Superscript left parenthesis 2 right parenthesis Average number of songs user purchased per week in the past
income bold x Superscript left parenthesis 1 right parenthesis, bold x Superscript left parenthesis 2 right parenthesis User’s income
price T Price that user was exposed to during the discount season (baseline price * small discount)
demand Y Number of songs user purchased during the discount season


This example has three goals:

  1. Estimate the parameters of interest, such as average partial effect of price on demand.

  2. Estimate the price elasticity of demand for songs, and assess its variation with users’ characteristics.

  3. 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 alpha left parenthesis dot right parenthesis and beta left parenthesis dot right parenthesis, 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, alpha left parenthesis dot right parenthesis information, and beta left parenthesis dot right parenthesis 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

The DEEPPRICE Procedure

Parameter Estimates
ParameterEstimateStandard
Error
95% Confidence LimitsZPr > |Z|
Average Intercept23.6296980.21506423.20818024.051217109.87<.0001
Average Slope-9.2358950.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 bold x Subscript i, so that the heterogeneous elasticity function is defined for the ith user as

elasticity left parenthesis bold x Subscript i Baseline right parenthesis equals beta left parenthesis bold x Subscript i Baseline right parenthesis StartFraction price Subscript i Baseline Over demand Subscript i Baseline EndFraction

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

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

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 (price Subscript i) in order to maximize the revenue, which is defined as

Revenue left parenthesis price Subscript i Baseline comma bold x Subscript i Baseline right parenthesis equals left parenthesis alpha left parenthesis bold x Subscript i Baseline right parenthesis plus beta left parenthesis bold x Subscript i Baseline right parenthesis asterisk price Subscript i Baseline right parenthesis asterisk price Subscript i Baseline

The optimal price for each user is therefore derived as price Subscript i Superscript asterisk Baseline equals minus StartFraction alpha left parenthesis bold x Subscript i Baseline right parenthesis Over 2 beta left parenthesis bold x Subscript i Baseline right parenthesis EndFraction. Let s1 be the optimal policy that sets each user’s price as price Subscript i Superscript asterisk. 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

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, alpha left parenthesis dot right parenthesis information, and beta left parenthesis dot right parenthesis 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

The DEEPPRICE Procedure

Policy Evaluation
PolicyEstimateStandard
Error
95% Confidence LimitsZPr > |Z|
price14.6489110.07315514.50552914.792293200.24<.0001
s014.3938040.07770214.24151114.546096185.24<.0001
s112.0174830.65986610.72416913.31079818.21<.0001
s1d16.2712190.05948216.15463716.387800273.55<.0001
s214.8698970.06984114.73301115.006784212.91<.0001
s314.8555980.07218014.71412814.997069205.81<.0001
s415.2202080.06939115.08420515.356211219.34<.0001
s515.9852900.07062715.84686316.123717226.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
EstimateStandard
Error
95% Confidence LimitsZPr > |Z|
s0price-0.2551080.035676-0.325032-0.185183-7.15<.0001
s1price-2.6314280.671764-3.948062-1.314794-3.92<.0001
s1dprice1.6223070.0415491.5408721.70374239.05<.0001
s2price0.2209860.0299490.1622870.2796857.38<.0001
s3price0.2066870.0278700.1520630.2613117.42<.0001
s4price0.5712960.0379460.4969240.64566915.06<.0001
s5price1.3363790.0357831.2662451.40651237.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

PolicyEstimateStdErrLowerCLUpperCLProbZ
price15.03780.0663914.907715.1679<.0001
s015.17650.0731915.033115.3200<.0001
s134.40070.3331833.747735.0537<.0001
s1d15.67640.0675015.544115.8087<.0001
s215.40180.0702415.264115.5395<.0001
s314.85070.0667114.719914.9814<.0001
s415.46530.0693715.329415.6013<.0001
s513.76140.0538413.655813.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

ComparisonPolicyBasePolicyEstimateStdErrLowerCLUpperCLProbZ
s0price0.13870.09881-0.05490.33240.1603
s1price19.36290.3397318.697020.0287<.0001
s1dprice0.63850.094670.45300.8241<.0001
s2price0.36400.096650.17450.55340.0002
s3price-0.18720.09411-0.3716-0.00270.0467
s4price0.42750.096020.23930.6157<.0001
s5price-1.27640.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 alpha left parenthesis dot right parenthesis and beta left parenthesis dot right parenthesis.

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;

Output 16.1.9: Estimated vs. True Intercept Function alpha left parenthesis dot right parenthesis

Estimated vs. True Intercept Function α(·)


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;

Output 16.1.10: Estimated and True Slope Function beta left parenthesis dot right parenthesis vs. Income

Estimated and True Slope Function β(·) vs. Income


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

PolicyEstimateStdErrLowerCLUpperCLProbZ
price14.34930.0669714.218114.4806<.0001
s014.29460.0748014.148014.4412<.0001
s125.99090.2076525.583926.3979<.0001
s1star29.22300.2210028.789829.6561<.0001
s1d14.95630.0675914.823815.0888<.0001
s1dstar15.03420.0668414.903215.1652<.0001
s214.60580.0711714.466314.7453<.0001
s314.05980.0679613.926614.1930<.0001
s414.72210.0698814.585114.8591<.0001
s513.17140.0542313.065113.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

ComparisonPolicyBasePolicyEstimateStdErrLowerCLUpperCLProbZ
s0price-0.05470.10040-0.25150.14210.5856
s1price11.64160.2181811.213912.0692<.0001
s1starprice14.87360.2309314.421015.3262<.0001
s1dprice0.60700.095150.42050.7935<.0001
s1dstarprice0.68490.094620.49940.8703<.0001
s2price0.25640.097730.06490.44800.0087
s3price-0.28950.09542-0.4766-0.10250.0024
s4price0.37280.096790.18300.56250.0001
s5price-1.17790.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.

Last updated: July 09, 2026