blog

SAS Coding: Using the DS2 Procedure

01/22/2016 by Maria Nicholson

As I recall when I first started using SAS, and I’m sure most programmers new to SAS would agree, the proprietary DATA step was a foreign and non-standard concept. I grew up on top-down structured programming and picked up objected-oriented design on-the-job along the way (who remembers SAS/AF and SCL?).

OOP has been an industry mainstay for many years, so I was pleased to learn about the new SAS 9.4 DS2 language. DS2 provides an object-oriented environment for the DATA step, which will likely lead to a more natural transition for traditional programmers delving into SAS.

DS2 is a new SAS proprietary programming language that is appropriate for advanced data manipulation. DS2 is included with Base SAS and intersects with the SAS DATA step. It also includes additional data types, ANSI SQL types, programming structure elements, and user-defined methods and packages. – SAS® 9.4 DS2 Language Reference

There is a lot to discover about DS2 — threaded processing and the predefined packages are particularly intriguing — but “user-defined methods and packages” is the phrase that initially caught my eye. That sounded to me like a great way to define, for example, a reusable package (class) of standard business rules (methods) for use in ETL or reporting. I decided to try it out on a straight forward product warranty and refund calculation.

Getting Started

The hypothetical warranty works like this: If you’ve owned an appliance for 30 days or less, then you can return it for a full refund of the purchase price. From 31 to 365 days, you can return it, but your refund amount will be the purchase price less $10.00 times the number of days you’ve owned it or $0, whichever is higher. After 365 days you receive no refund.

My goal was to build a reusable module that could be called many times by other programmers without their needing to know the particulars of the warranty, but giving them the freedom to make some changes to the assumptions. To that end, this is what I hoped DS2 would allow me to do:

  • Define a package called warranty and save it permanently for later use.
  • Include a set of warranty properties:
    • Daily Rate – daily dollar amount subtracted from the original purchase price
    • Min Days – number of days since purchase up to which a full refund can be granted
    • Max Days – number of days since purchase after which zero refund is granted
    • Return Date – date product is returned for refund
  • When the warranty package is instantiated, automatically set default values for the properties:
    • Daily Rate = $10.00
    • Min Days = 30
    • Max Days = 365
    • Return Date = today
  • Allow programmers to override the default property values, as required.
  • Include a refund method that accepts the purchase price and date purchased and returns the number of days elapsed since purchase and the refund amount.

Let’s take a look at how the warranty package can be defined to meet these requirements. If you are unfamiliar with the basics of DS2, pause here for a nice overview provided by SAS Global Forum paper DS2 with Both Hands on the Wheel.

Coding the DS2 Package

  1. A quick look using the SAS Explorer window shows that a DS2 user-defined package is saved as encrypted text within a SAS data set. You save the package permanently by using a SAS LIBNAME as the first level of the package name.
  2. The warranty package properties dailyRate, minDays, maxDays, and returnDate are declared as variables global to the package. Within the methods you’ll notice that these package global variables are identified using “this.” to distinguish them from any possible local method variables of the same name.
  3. A method with the same name as the package is a constructor. The warranty method is the warranty package constructor.
  4. DS2 allows method overloading. Note that there are three definitions of the warranty method. The first takes no arguments and is the default constructor. It will assign the package properties default values upon instantiation. The other two warranty method definitions provide options for instantiating the warranty package with user-defined values for dailyRate, minDays, maxDays, and returnDate.
  5. The refund method encapsulates the refund calculation of business rule. Note that DS2 allows you to pass arguments to methods by value or by reference. Use the “in_out” keyword to indicate by reference. With the refund method, I want the number of days elapsed since purchase and the refund amount to be returned to the calling DATA step so I have those arguments designated as “in_out”.

Using the SAS DS2 Package

Now that the warranty package is defined, let’s see how it computes refunds using the data set example.products.

Below is a DS2 code block that creates the data set work.refunds. The warranty package default constructor is used to instantiate the object w. Two new variables, refund and elapsedDays, are declared globally so that they can be included as columns in work.refunds. For each row in example.products, the refund method is called. It computes refund and elapsedDays using the purchase_price and date_of_purchase. Records are implicitly output to work.refunds.

libname example 'C:ProjectsZencosblogds2'; /* reference the library where the warranty package is stored */proc ds2;data work.refunds / overwrite=yes;  /* create w as an instance of the warranty package with default constructor */  dcl package example.warranty w();    /* create global instances of two variables, refund and elapsedDays.     These new variables are output to work.refunds */  dcl double refund having format dollar20.2;  dcl double elapsedDays;  method run();set example.products;/* use the refund method to compute elapsedDays and refund using the purchase_price and date_of_purchase *//* convert SAS date to double type */w.refund(purchase_price,to_double(date_of_purchase),elapsedDays,refund);  end;run;quit;

The work.refunds DS2 code block has been modified to demonstrate how you can override the default property values by passing user-defined values to the constructor. Alternatively, you could change the value of a property through an assignment statement prior to the refund method call.

proc ds2;data work.refunds / overwrite=yes;  /* use constructor to override default values for the dailyRate and returnDate properties */  dcl package example.warranty w(20.00,to_double(date'2015-12-31'));  dcl double refund having format dollar20.2;  dcl double elapsedDays;  method run();set example.products;w.maxDays=100; /* you can also change a property value this way too */w.refund(purchase_price,to_double(date_of_purchase),elapsedDays,refund);  end;run;

Beyond the Basics

I am excited about discovering ways to use Proc DS2, its object-oriented design, and promising data processing performance improvements in my SAS 9.4 applications. I also look forward to sharing more DS2 insights and examples on the Zencos blog.