Efficiency Analysis: How to Use Data Envelopment Analysis to Compare Efficiencies of Garages


PROC OPTMODEL Statements and Output

The first several PROC OPTMODEL statements declare index sets and parameters and then read the input data:

proc optmodel;
   set <str> INPUTS;
   read data inputs into INPUTS=[input];

   set <str> OUTPUTS;
   read data outputs into OUTPUTS=[output];

   set <num> GARAGES;
   str garage_name {GARAGES};
   num input  {INPUTS, GARAGES};
   num output {OUTPUTS, GARAGES};
   read data garage_data into GARAGES=[_N_] garage_name
      {i in INPUTS}  <input[i,_N_]=col(i)>
      {i in OUTPUTS} <output[i,_N_]=col(i)>;

   num k;
   num efficiency_number {GARAGES};
   num weight_sol {GARAGES, GARAGES};

The following statements correspond directly to the mathematical programming formulation that is described earlier:

   var Weight {GARAGES} >= 0;
   var Inefficiency >= 0;

   max Objective = Inefficiency;

   con Input_con {i in INPUTS}:
      sum {j in GARAGES} input[i,j] * Weight[j] <= input[i,k];

   con Output_con {i in OUTPUTS}:
      sum {j in GARAGES} output[i,j] * Weight[j] >= output[i,k] * Inefficiency;

The following statements loop over all garages, call the linear programming solver once per garage, and store the results in the parameters efficiency_number and weight_sol:

   do k = GARAGES;
      solve;
      efficiency_number[k] = 1 / Inefficiency.sol;
      for {j in GARAGES}
         weight_sol[k,j] = (if Weight[j].sol > 1e-6 then Weight[j].sol else .);
   end;

Note that the DO loop contains no declaration statements. As the value of k changes, the SOLVE statement automatically updates the constraints to use the values of input[i,k] and output[i,k]. The approach shown here is much more efficient than the following alternatives:

  • Calling PROC OPTMODEL once per garage

  • Enlarging the set of decision variables by including an additional index, resulting in variables Weight[k,j] and Inefficiency[k]. Within the DO loop, you must then fix most of the variables to 0 and rely on the presolver to remove them, but that approach uses much more memory and computational time.

In SAS/OR 13.1, you can instead solve the same set of problems in parallel by replacing the DO loop with the following COFOR loop:

   cofor {kk in GARAGES} do;
      k = kk;
      solve;
      efficiency_number[k] = 1 / Inefficiency.sol;
      for {j in GARAGES}
         weight_sol[k,j] = (if Weight[j].sol > 1e-6 then Weight[j].sol else .);
   end;

After the DO or COFOR loop terminates, the following statements partition the garages into two sets by using a threshold on the resulting efficiency numbers:

   set EFFICIENT_GARAGES = {j in GARAGES: efficiency_number[j] >= 1};
   set INEFFICIENT_GARAGES = GARAGES diff EFFICIENT_GARAGES;

The following statements print the efficiency numbers, as shown in Figure 22.1, and write them to the efficiency_data data set:

   print garage_name efficiency_number;
   create data efficiency_data from [garage] garage_name efficiency_number;

Figure 22.1: efficiency_number Parameter

The OPTMODEL Procedure

[1] garage_name efficiency_number
1 Winchester 0.84017
2 Andover 0.91738
3 Basingstoke 1.00000
4 Poole 0.86189
5 Woking 0.86732
6 Newbury 1.00000
7 Portsmouth 1.00000
8 Alresford 1.00000
9 Salisbury 1.00000
10 Guildford 0.81417
11 Alton 1.00000
12 Weybridge 0.85435
13 Dorchester 0.83920
14 Bridport 0.97101
15 Weymouth 1.00000
16 Portland 1.00000
17 Chichester 0.82434
18 Petersfield 1.00000
19 Petworth 0.98824
20 Midhurst 0.82928
21 Reading 0.98205
22 Southampton 1.00000
23 Bournemouth 1.00000
24 Henley 1.00000
25 Maidenhead 1.00000
26 Fareham 1.00000
27 Romsey 1.00000
28 Ringwood 0.87587



The following CREATE DATA statements write the inefficient garages and the corresponding multiples of efficient garages to SAS data sets (in both dense and sparse form), as in Table 14.8 in Williams (1999):

   create data weight_data_dense from [inefficient_garage]=INEFFICIENT_GARAGES
      garage_name
      efficiency_number
      {efficient_garage in EFFICIENT_GARAGES} <col('w'||efficient_garage)
         =weight_sol[inefficient_garage,efficient_garage]>;
   create data weight_data_sparse from
      [inefficient_garage efficient_garage]=
   {g1 in INEFFICIENT_GARAGES, g2 in EFFICIENT_GARAGES: weight_sol[g1,g2] ne .}
      weight_sol;
quit;

The following statements sort the efficiency_data data set by efficiency and print the results, shown in Figure 22.2:

proc sort data=efficiency_data;
   by descending efficiency_number;
run;

proc print;
run;

Figure 22.2: efficiency_data Data Set

Obs garage garage_name efficiency_number
1 25 Maidenhead 1.00000
2 24 Henley 1.00000
3 3 Basingstoke 1.00000
4 6 Newbury 1.00000
5 7 Portsmouth 1.00000
6 8 Alresford 1.00000
7 9 Salisbury 1.00000
8 11 Alton 1.00000
9 15 Weymouth 1.00000
10 16 Portland 1.00000
11 18 Petersfield 1.00000
12 22 Southampton 1.00000
13 23 Bournemouth 1.00000
14 26 Fareham 1.00000
15 27 Romsey 1.00000
16 19 Petworth 0.98824
17 21 Reading 0.98205
18 14 Bridport 0.97101
19 2 Andover 0.91738
20 28 Ringwood 0.87587
21 5 Woking 0.86732
22 4 Poole 0.86189
23 12 Weybridge 0.85435
24 1 Winchester 0.84017
25 13 Dorchester 0.83920
26 20 Midhurst 0.82928
27 17 Chichester 0.82434
28 10 Guildford 0.81417



The following statements sort the weight_data_dense data set by efficiency and print the results, shown in Figure 22.3:

proc sort data=weight_data_dense;
   by descending efficiency_number;
run;

proc print;
run;

Figure 22.3: weight_data_dense Data Set

Obs inefficient_garage garage_name efficiency_number w3 w6 w7 w8 w9 w11 w15 w16 w18 w22 w23 w24 w25 w26 w27
1 19 Petworth 0.98824 . 0.066345 . . . . . . 0.015212 . . . 0.03409 0.67493 .
2 21 Reading 0.98205 1.26862 . . . . . 0.54441 1.19914 . . . 2.86247 0.13753 . .
3 14 Bridport 0.97101 0.03278 . . . . . . 0.46969 . . . 0.78310 0.19489 . .
4 2 Andover 0.91738 . . . . . . 0.85714 . . . . . 0.21429 . .
5 28 Ringwood 0.87587 0.00771 . . . . . . 0.31973 . . . 0.14649 . . .
6 5 Woking 0.86732 . . . 0.95253 . 0.021078 . . . .009092662 . . 0.14838 . .
7 4 Poole 0.86189 0.32859 . . . . . . 0.75733 . . . 0.43442 0.34463 . .
8 12 Weybridge 0.85435 . . . . . . 0.79656 . . . . . 0.14524 0.01773 .
9 1 Winchester 0.84017 . . 0.00528 0.41627 0.40328 . 0.33333 0.09614 . . . . . . .
10 13 Dorchester 0.83920 0.13436 . . 0.10448 . . 0.11929 0.75163 . . . 0.03532 . 0.47905 .
11 20 Midhurst 0.82928 . . . . 0.05957 . 0.06651 0.47189 0.043482 . . . 0.00894 . .
12 17 Chichester 0.82434 0.05825 . . 0.09682 . . 0.33543 0.16523 . . . 0.23637 . 0.15424 .
13 10 Guildford 0.81417 0.42459 . 0.14961 0.62272 . . 0.19180 0.16807 . . . . . . .



The weight_data_sparse data set contains the same information in sparse format, as shown in Figure 22.4:

proc print data=weight_data_sparse;
run;

Figure 22.4: weight_data_sparse Data Set

Obs inefficient_garage efficient_garage weight_sol
1 1 7 0.00528
2 1 8 0.41627
3 1 9 0.40328
4 1 15 0.33333
5 1 16 0.09614
6 2 15 0.85714
7 2 25 0.21429
8 4 3 0.32859
9 4 16 0.75733
10 4 24 0.43442
11 4 25 0.34463
12 5 8 0.95253
13 5 11 0.02108
14 5 22 0.00909
15 5 25 0.14838
16 10 3 0.42459
17 10 7 0.14961
18 10 8 0.62272
19 10 15 0.19180
20 10 16 0.16807
21 12 15 0.79656
22 12 25 0.14524
23 12 26 0.01773
24 13 3 0.13436
25 13 8 0.10448
26 13 15 0.11929
27 13 16 0.75163
28 13 24 0.03532
29 13 26 0.47905
30 14 3 0.03278
31 14 16 0.46969
32 14 24 0.78310
33 14 25 0.19489
34 17 3 0.05825
35 17 8 0.09682
36 17 15 0.33543
37 17 16 0.16523
38 17 24 0.23637
39 17 26 0.15424
40 19 6 0.06635
41 19 18 0.01521
42 19 25 0.03409
43 19 26 0.67493
44 20 9 0.05957
45 20 15 0.06651
46 20 16 0.47189
47 20 18 0.04348
48 20 25 0.00894
49 21 3 1.26862
50 21 15 0.54441
51 21 16 1.19914
52 21 24 2.86247
53 21 25 0.13753
54 28 3 0.00771
55 28 16 0.31973
56 28 24 0.14649