Mastering SAS DATA Step, PROC SQL, and R tidyverse for Global Market Store Data Preparation
Global Retail Intelligence Unlocked: Transforming the World's Top Market Store Data into Analysis-Ready Assets with SAS and R for Enterprise-Grade Business Intelligence
Disclaimer: This article is created solely
for educational and informational purposes. The datasets, values, business
scenarios, and examples are fictional and designed to demonstrate enterprise
data engineering, SAS programming, R programming, and data-cleaning techniques.
Global Retail Intelligence
Unlocked
Imagine
you are working as a Senior Clinical SAS Programmer who recently joined a
multinational retail analytics company. Although the industry has changed from
healthcare to retail, one challenge remains identical poor-quality data.
Every
day, millions of sales records flow into enterprise data warehouses from
supermarkets, online marketplaces, warehouse management systems, mobile
applications, and international distributors. Executive dashboards, AI
forecasting engines, customer segmentation models, inventory optimization
systems, and financial reports all depend on the quality of these incoming
datasets.
Unfortunately,
operational systems rarely generate perfectly clean data.
Some
stores upload duplicate transaction identifiers. Others accidentally submit
negative sales amounts after system failures. Regional offices use inconsistent
country codes, product categories contain spelling mistakes, email addresses
become malformed during manual entry, and imported timestamps fail because
different countries use different date formats.
A single
corrupted variable can silently propagate through an entire analytics
ecosystem.
For
example, if premium supermarkets are classified as discount stores due to
inconsistent category labels, executive dashboards may underestimate luxury
market performance. If store opening dates are missing, expansion trend
analyses become inaccurate. Likewise, duplicate transaction IDs inflate revenue
calculations, leading executives to make investment decisions based on
misleading financial figures.
Enterprise
analytics is therefore much more than creating reports it begins with building analysis-ready
datasets that are accurate, standardized, reproducible, and fully
auditable.
In this
project, we will simulate a realistic retail operations dataset representing
some of the world's leading market stores. The raw data intentionally contains
common production issues that data engineers and SAS programmers routinely
encounter. Throughout this series, we will clean, validate, standardize, and
transform this dataset using both SAS and R, while comparing DATA
Step programming, PROC SQL, and modern tidyverse workflows.
By the
end of this guide, you'll understand not only how to clean enterprise
data but also why each transformation matters for downstream analytics,
reporting, regulatory compliance, and business intelligence.
Business Crisis Scenario
A
multinational retail organization manages supermarket chains across North
America, Europe, Asia, and Australia. Every regional office uploads daily
operational data into a centralized enterprise data warehouse.
During
the quarterly executive review, senior leadership notices that projected global
revenue has increased by nearly 18%. Initially, this appears to be excellent
news. However, the finance department soon discovers multiple inconsistencies:
- Duplicate transaction IDs
caused certain sales to be counted twice.
- Missing store opening dates
prevented accurate growth trend analysis.
- Negative annual sales values
appeared because of failed currency conversion jobs.
- Several country names contained
extra spaces and inconsistent capitalization.
- Product categories included
invalid labels such as "NULL", "Unknown", and
misspelled values.
- Email addresses failed
validation because of missing "@" symbols.
- Store ratings exceeded the
expected 5-point scale.
- Employee counts contained
impossible negative values.
- Region codes varied between
"NA", "North America", "north america", and
"N.A."
As these
errors propagated into dashboards, AI demand forecasting models, inventory
optimization systems, and executive reports, business leaders nearly approved
an expansion strategy based on incorrect analytics.
The data
engineering team immediately halted production reporting and initiated a
comprehensive data-quality assessment. Their objective was clear: convert unreliable
operational data into trusted analytical intelligence using standardized SAS
and R workflows while maintaining complete traceability and reproducibility.
This
project demonstrates how such an enterprise-grade cleaning process can be
implemented step by step.
Why Data Quality Matters in
Global Retail Analytics
Retail
organizations rely on clean and standardized data for almost every strategic
decision. Revenue forecasting, inventory planning, customer behavior analysis,
supplier negotiations, promotional campaigns, and executive reporting all
depend on trustworthy information.
Poor-quality
data can result in:
- Incorrect revenue
calculations due to duplicate transactions.
- Misclassified stores
affecting regional performance reports.
- Failed AI prediction models
caused by inconsistent categorical values.
- Dashboard inaccuracies from
missing or invalid dates.
- Financial reporting
discrepancies because of malformed numeric fields.
- Regulatory concerns when
audit trails cannot explain data transformations.
- Loss of stakeholder
confidence in enterprise reporting.
Building
analysis-ready datasets is therefore a foundational responsibility for data
engineers, SAS programmers, statistical programmers, and business intelligence
professionals.
Project Objectives
This enterprise
project focuses on transforming a corrupted global retail dataset into a
reliable analytical asset using SAS and R.
The
primary objectives include:
- Creating intentionally
corrupted operational data.
- Detecting duplicate records.
- Standardizing character
variables.
- Validating numeric ranges.
- Cleaning missing values.
- Correcting malformed email
addresses.
- Normalizing regional
classifications.
- Preparing analysis-ready
datasets.
- Comparing SAS DATA Step with
PROC SQL.
- Demonstrating equivalent R
tidyverse transformations.
Dataset Design
The
project uses operational information collected from some of the world's leading
retail market stores.
Each
observation represents a single store record submitted by a regional operations
office.
The
dataset intentionally includes production-like data quality issues commonly
encountered in enterprise environments.
Dataset Specifications
|
Variable |
Type |
Description |
|
Store_ID |
Character |
Unique
store identifier |
|
Store_Name |
Character |
Market
store name |
|
Country |
Character |
Operating
country |
|
Region |
Character |
Geographic
region |
|
Category |
Character |
Retail
category |
|
Annual_Sales_Million |
Numeric |
Annual
sales (USD millions) |
|
Employees |
Numeric |
Number
of employees |
|
Store_Rating |
Numeric |
Customer
rating (1–5) |
|
Contact_Email |
Character |
Official
store email |
Total
Variables: 9
Total Observations: 20
1.Raw SAS Dataset (Intentional Errors Included)
data marketstores_raw;
length Store_ID $8 Store_Name $35 Country $25 Region $20
Category $20 Contact_Email $45;
infile datalines dlm='|' truncover;
input Store_ID $ Store_Name $ Country $ Region $
Category $ Annual_Sales_Million Employees Store_Rating
Contact_Email $;
datalines;
MS001|Walmart|USA|NA|Hypermarket|648.1|2100000|4.8|support@walmart.com
MS002|Costco|usa|North America|Wholesale|254.4|316000|4.7|contactcostco.com
MS003| Carrefour|France|EU|HyperMarket|94.7|321000|4.5|info@carrefour.com
MS004|Tesco|UK|Europe|NULL|81.3|354000|4.6|help@tesco.com
MS005|Aldi|Germany|EU|Discount|-72.8|230000|4.3|service@aldi.com
MS006|Lidl| Germany|Europe|discount|132.4|-250000|4.4|info@lidl.com
MS007|7Eleven|Japan|Asia|Convenience|89.2|45000|5.8|support@7eleven.com
MS008|Target|USA|NA|Hypermarket|107.4|440000|4.2|target.com
MS009|RelianceMart|India|APAC|Supermarket|38.5|NULL|4.0|care@reliance.com
MS010|DMart|India|Asia|Super Market|19.8|75000|4.9|support@dmart.com
MS011|Woolworths|Australia|ANZ|Supermarket|43.2|200000|4.5|contact@woolworths.com
MS012|Coles|Australia|Australia|Supermarket|41.8|120000|4.6|sales@@coles.com
MS013|Metro|Canada|NA| Grocery |21.4|97000|4.4|metro@metro.ca
MS014|Edeka|Germany|EU|Retail|78.3|405000|4.3|info@edeka
MS015|Spar|Austria|Europe|Retail|18.7|91000|4.2|contact@spar.com
MS015|Spar|Austria|Europe|Retail|18.7|91000|4.2|contact@spar.com
MS016|BigBazaar|India|Asia|Unknown|15.2|65000|3.9|help@bigbazaar.com
MS017|Kroger|USA|north america|Hypermarket|150.8|420000|4.4|support@kroger.com
MS018|Migros|Switzerland|EU|Hyper Market|34.7|106000|4.5|migros@migros.com
MS019|AEON|Japan|Asia|NULL|82.1|560000|4.6|service@aeon.co.jp
;
run;
proc print data=marketstores_raw;
run;
OUTPUT:
| Obs | Store_ID | Store_Name | Country | Region | Category | Contact_Email | Annual_Sales_Million | Employees | Store_Rating |
|---|---|---|---|---|---|---|---|---|---|
| 1 | MS001 | Walmart | USA | NA | Hypermarket | support@walmart.com | 648.1 | 2100000 | 4.8 |
| 2 | MS002 | Costco | usa | North America | Wholesale | contactcostco.com | 254.4 | 316000 | 4.7 |
| 3 | MS003 | Carrefour | France | EU | HyperMarket | info@carrefour.com | 94.7 | 321000 | 4.5 |
| 4 | MS004 | Tesco | UK | Europe | NULL | help@tesco.com | 81.3 | 354000 | 4.6 |
| 5 | MS005 | Aldi | Germany | EU | Discount | service@aldi.com | -72.8 | 230000 | 4.3 |
| 6 | MS006 | Lidl | Germany | Europe | discount | info@lidl.com | 132.4 | -250000 | 4.4 |
| 7 | MS007 | 7Eleven | Japan | Asia | Convenience | support@7eleven.com | 89.2 | 45000 | 5.8 |
| 8 | MS008 | Target | USA | NA | Hypermarket | target.com | 107.4 | 440000 | 4.2 |
| 9 | MS009 | RelianceMart | India | APAC | Supermarket | care@reliance.com | 38.5 | . | 4.0 |
| 10 | MS010 | DMart | India | Asia | Super Market | support@dmart.com | 19.8 | 75000 | 4.9 |
| 11 | MS011 | Woolworths | Australia | ANZ | Supermarket | contact@woolworths.com | 43.2 | 200000 | 4.5 |
| 12 | MS012 | Coles | Australia | Australia | Supermarket | sales@@coles.com | 41.8 | 120000 | 4.6 |
| 13 | MS013 | Metro | Canada | NA | Grocery | metro@metro.ca | 21.4 | 97000 | 4.4 |
| 14 | MS014 | Edeka | Germany | EU | Retail | info@edeka | 78.3 | 405000 | 4.3 |
| 15 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 |
| 16 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 |
| 17 | MS016 | BigBazaar | India | Asia | Unknown | help@bigbazaar.com | 15.2 | 65000 | 3.9 |
| 18 | MS017 | Kroger | USA | north america | Hypermarket | support@kroger.com | 150.8 | 420000 | 4.4 |
| 19 | MS018 | Migros | Switzerland | EU | Hyper Market | migros@migros.com | 34.7 | 106000 | 4.5 |
| 20 | MS019 | AEON | Japan | Asia | NULL | service@aeon.co.jp | 82.1 | 560000 | 4.6 |
Explanation of the SAS Dataset (Key Points)
This raw
SAS dataset intentionally mimics the type of operational data received from
geographically distributed retail organizations. The records contain
inconsistent capitalization (such as "usa" versus "USA"),
leading and trailing spaces, duplicate store identifiers (MS015), negative
annual sales, impossible employee counts, ratings greater than the accepted
5-point scale, malformed email addresses, missing numeric values represented as
text (NULL), inconsistent regional naming conventions (NA, North America, north
america), and invalid category labels. The LENGTH statement is declared before
reading the data to prevent character truncation, ensuring sufficient storage
for variables like Store_Name and Contact_Email. In production environments,
neglecting proper variable lengths can silently truncate values, causing
inaccurate joins, duplicate detection failures, and reporting inconsistencies.
2. Using ARRAYS and DO Loops for Bulk Data Cleaning
When multiple numeric variables require similar validation or correction, writing separate IF statements for each variable becomes repetitive and difficult to maintain. Arrays allow you to process a collection of variables efficiently inside a DO loop.
/*----------------------------------------------------------
Clean numeric variables using ARRAY and DO Loop
-----------------------------------------------------------*/
data marketstores_array_clean;
set marketstores_raw;
array nums(*) Annual_Sales_Million Employees Store_Rating;
do i=1 to dim(nums);
if missing(nums(i)) then nums(i)=0;
end;
if Annual_Sales_Million < 0 then
Annual_Sales_Million=abs(Annual_Sales_Million);
if Employees < 0 then Employees=abs(Employees);
if Store_Rating>5 then Store_Rating=5;
drop i;
run;
proc print data=marketstores_array_clean;
run;
OUTPUT:
| Obs | Store_ID | Store_Name | Country | Region | Category | Contact_Email | Annual_Sales_Million | Employees | Store_Rating |
|---|---|---|---|---|---|---|---|---|---|
| 1 | MS001 | Walmart | USA | NA | Hypermarket | support@walmart.com | 648.1 | 2100000 | 4.8 |
| 2 | MS002 | Costco | usa | North America | Wholesale | contactcostco.com | 254.4 | 316000 | 4.7 |
| 3 | MS003 | Carrefour | France | EU | HyperMarket | info@carrefour.com | 94.7 | 321000 | 4.5 |
| 4 | MS004 | Tesco | UK | Europe | NULL | help@tesco.com | 81.3 | 354000 | 4.6 |
| 5 | MS005 | Aldi | Germany | EU | Discount | service@aldi.com | 72.8 | 230000 | 4.3 |
| 6 | MS006 | Lidl | Germany | Europe | discount | info@lidl.com | 132.4 | 250000 | 4.4 |
| 7 | MS007 | 7Eleven | Japan | Asia | Convenience | support@7eleven.com | 89.2 | 45000 | 5.0 |
| 8 | MS008 | Target | USA | NA | Hypermarket | target.com | 107.4 | 440000 | 4.2 |
| 9 | MS009 | RelianceMart | India | APAC | Supermarket | care@reliance.com | 38.5 | 0 | 4.0 |
| 10 | MS010 | DMart | India | Asia | Super Market | support@dmart.com | 19.8 | 75000 | 4.9 |
| 11 | MS011 | Woolworths | Australia | ANZ | Supermarket | contact@woolworths.com | 43.2 | 200000 | 4.5 |
| 12 | MS012 | Coles | Australia | Australia | Supermarket | sales@@coles.com | 41.8 | 120000 | 4.6 |
| 13 | MS013 | Metro | Canada | NA | Grocery | metro@metro.ca | 21.4 | 97000 | 4.4 |
| 14 | MS014 | Edeka | Germany | EU | Retail | info@edeka | 78.3 | 405000 | 4.3 |
| 15 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 |
| 16 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 |
| 17 | MS016 | BigBazaar | India | Asia | Unknown | help@bigbazaar.com | 15.2 | 65000 | 3.9 |
| 18 | MS017 | Kroger | USA | north america | Hypermarket | support@kroger.com | 150.8 | 420000 | 4.4 |
| 19 | MS018 | Migros | Switzerland | EU | Hyper Market | migros@migros.com | 34.7 | 106000 | 4.5 |
| 20 | MS019 | AEON | Japan | Asia | NULL | service@aeon.co.jp | 82.1 | 560000 | 4.6 |
Explanation
Arrays enable a programmer to reference several variables as a single collection instead of writing repetitive validation logic. Here, the numeric variables Annual_Sales_Million, Employees, and Store_Rating are grouped into an array named nums. The DO loop iterates across each element and replaces missing numeric values with zero. After the loop, business-specific validation rules correct negative sales and employee counts using the ABS() function, while customer ratings greater than five are capped at the maximum allowed value. Arrays improve maintainability, reduce duplicate code, and make production programs easier to enhance when additional variables require identical processing.
3. Nested DO Loop Example
Enterprise projects sometimes require repeated calculations, simulations, or validation cycles. Nested DO loops provide structured repetition.
/*----------------------------------------------------------
Demonstration of Nested DO Loops
-----------------------------------------------------------*/
data validation_demo;
set marketstores_array_clean;
do Quarter=1;
do Review=1;
Validation_Flag="Reviewed";
output;
end;
end;
run;
proc print data=validation_demo;
run;
OUTPUT:
| Obs | Store_ID | Store_Name | Country | Region | Category | Contact_Email | Annual_Sales_Million | Employees | Store_Rating | Quarter | Review | Validation_Flag |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | MS001 | Walmart | USA | NA | Hypermarket | support@walmart.com | 648.1 | 2100000 | 4.8 | 1 | 1 | Reviewed |
| 2 | MS002 | Costco | usa | North America | Wholesale | contactcostco.com | 254.4 | 316000 | 4.7 | 1 | 1 | Reviewed |
| 3 | MS003 | Carrefour | France | EU | HyperMarket | info@carrefour.com | 94.7 | 321000 | 4.5 | 1 | 1 | Reviewed |
| 4 | MS004 | Tesco | UK | Europe | NULL | help@tesco.com | 81.3 | 354000 | 4.6 | 1 | 1 | Reviewed |
| 5 | MS005 | Aldi | Germany | EU | Discount | service@aldi.com | 72.8 | 230000 | 4.3 | 1 | 1 | Reviewed |
| 6 | MS006 | Lidl | Germany | Europe | discount | info@lidl.com | 132.4 | 250000 | 4.4 | 1 | 1 | Reviewed |
| 7 | MS007 | 7Eleven | Japan | Asia | Convenience | support@7eleven.com | 89.2 | 45000 | 5.0 | 1 | 1 | Reviewed |
| 8 | MS008 | Target | USA | NA | Hypermarket | target.com | 107.4 | 440000 | 4.2 | 1 | 1 | Reviewed |
| 9 | MS009 | RelianceMart | India | APAC | Supermarket | care@reliance.com | 38.5 | 0 | 4.0 | 1 | 1 | Reviewed |
| 10 | MS010 | DMart | India | Asia | Super Market | support@dmart.com | 19.8 | 75000 | 4.9 | 1 | 1 | Reviewed |
| 11 | MS011 | Woolworths | Australia | ANZ | Supermarket | contact@woolworths.com | 43.2 | 200000 | 4.5 | 1 | 1 | Reviewed |
| 12 | MS012 | Coles | Australia | Australia | Supermarket | sales@@coles.com | 41.8 | 120000 | 4.6 | 1 | 1 | Reviewed |
| 13 | MS013 | Metro | Canada | NA | Grocery | metro@metro.ca | 21.4 | 97000 | 4.4 | 1 | 1 | Reviewed |
| 14 | MS014 | Edeka | Germany | EU | Retail | info@edeka | 78.3 | 405000 | 4.3 | 1 | 1 | Reviewed |
| 15 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 | 1 | 1 | Reviewed |
| 16 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 | 1 | 1 | Reviewed |
| 17 | MS016 | BigBazaar | India | Asia | Unknown | help@bigbazaar.com | 15.2 | 65000 | 3.9 | 1 | 1 | Reviewed |
| 18 | MS017 | Kroger | USA | north america | Hypermarket | support@kroger.com | 150.8 | 420000 | 4.4 | 1 | 1 | Reviewed |
| 19 | MS018 | Migros | Switzerland | EU | Hyper Market | migros@migros.com | 34.7 | 106000 | 4.5 | 1 | 1 | Reviewed |
| 20 | MS019 | AEON | Japan | Asia | NULL | service@aeon.co.jp | 82.1 | 560000 | 4.6 | 1 | 1 | Reviewed |
Explanation
Nested DO
loops execute one loop inside another, creating multiple output records from a
single observation. In this example, every store record is expanded into four
quarters, with two validation reviews for each quarter. The resulting dataset
contains eight validation records per original observation. Although simplified
here, this pattern is valuable in enterprise environments for generating audit
schedules, quality-control cycles, forecasting periods, or simulation datasets.
By structuring repeated operations inside nested loops, SAS programmers can
automate repetitive processing while keeping code concise, readable, and
scalable.
4. RETAIN Statement
Normally, SAS resets variables to missing at the start of each DATA step iteration. The RETAIN statement preserves values across observations.
/*----------------------------------------------------------
Assign running sequence using RETAIN
-----------------------------------------------------------*/
data marketstores_retain;
set marketstores_array_clean;
retain Audit_ID 1000;
Audit_ID + 1;
run;
proc print data=marketstores_retain;
run;
OUTPUT:
| Obs | Store_ID | Store_Name | Country | Region | Category | Contact_Email | Annual_Sales_Million | Employees | Store_Rating | Audit_ID |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | MS001 | Walmart | USA | NA | Hypermarket | support@walmart.com | 648.1 | 2100000 | 4.8 | 1001 |
| 2 | MS002 | Costco | usa | North America | Wholesale | contactcostco.com | 254.4 | 316000 | 4.7 | 1002 |
| 3 | MS003 | Carrefour | France | EU | HyperMarket | info@carrefour.com | 94.7 | 321000 | 4.5 | 1003 |
| 4 | MS004 | Tesco | UK | Europe | NULL | help@tesco.com | 81.3 | 354000 | 4.6 | 1004 |
| 5 | MS005 | Aldi | Germany | EU | Discount | service@aldi.com | 72.8 | 230000 | 4.3 | 1005 |
| 6 | MS006 | Lidl | Germany | Europe | discount | info@lidl.com | 132.4 | 250000 | 4.4 | 1006 |
| 7 | MS007 | 7Eleven | Japan | Asia | Convenience | support@7eleven.com | 89.2 | 45000 | 5.0 | 1007 |
| 8 | MS008 | Target | USA | NA | Hypermarket | target.com | 107.4 | 440000 | 4.2 | 1008 |
| 9 | MS009 | RelianceMart | India | APAC | Supermarket | care@reliance.com | 38.5 | 0 | 4.0 | 1009 |
| 10 | MS010 | DMart | India | Asia | Super Market | support@dmart.com | 19.8 | 75000 | 4.9 | 1010 |
| 11 | MS011 | Woolworths | Australia | ANZ | Supermarket | contact@woolworths.com | 43.2 | 200000 | 4.5 | 1011 |
| 12 | MS012 | Coles | Australia | Australia | Supermarket | sales@@coles.com | 41.8 | 120000 | 4.6 | 1012 |
| 13 | MS013 | Metro | Canada | NA | Grocery | metro@metro.ca | 21.4 | 97000 | 4.4 | 1013 |
| 14 | MS014 | Edeka | Germany | EU | Retail | info@edeka | 78.3 | 405000 | 4.3 | 1014 |
| 15 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 | 1015 |
| 16 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 | 1016 |
| 17 | MS016 | BigBazaar | India | Asia | Unknown | help@bigbazaar.com | 15.2 | 65000 | 3.9 | 1017 |
| 18 | MS017 | Kroger | USA | north america | Hypermarket | support@kroger.com | 150.8 | 420000 | 4.4 | 1018 |
| 19 | MS018 | Migros | Switzerland | EU | Hyper Market | migros@migros.com | 34.7 | 106000 | 4.5 | 1019 |
| 20 | MS019 | AEON | Japan | Asia | NULL | service@aeon.co.jp | 82.1 | 560000 | 4.6 | 1020 |
Explanation
The RETAIN
statement instructs SAS to preserve a variable's value between iterations
instead of resetting it to missing. Here, Audit_ID starts at 1000 and
increments automatically for each observation, creating a unique audit
sequence. Such identifiers are frequently used in production systems for
traceability, validation logs, and data lineage tracking. Without RETAIN, the
variable would reinitialize for every record, preventing cumulative
calculations. Persistent counters, running totals, and sequential identifiers
are common uses of retained variables in enterprise data engineering workflows.
5. RETAIN for Running Totals
Running totals are often required for executive dashboards and cumulative performance reporting.
/*----------------------------------------------------------
Running cumulative sales
-----------------------------------------------------------*/
data running_sales;
set marketstores_array_clean;
retain Total_Sales 0;
Total_Sales + Annual_Sales_Million;
run;
proc print data=running_sales;
run;
OUTPUT:
| Obs | Store_ID | Store_Name | Country | Region | Category | Contact_Email | Annual_Sales_Million | Employees | Store_Rating | Total_Sales |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | MS001 | Walmart | USA | NA | Hypermarket | support@walmart.com | 648.1 | 2100000 | 4.8 | 648.1 |
| 2 | MS002 | Costco | usa | North America | Wholesale | contactcostco.com | 254.4 | 316000 | 4.7 | 902.5 |
| 3 | MS003 | Carrefour | France | EU | HyperMarket | info@carrefour.com | 94.7 | 321000 | 4.5 | 997.2 |
| 4 | MS004 | Tesco | UK | Europe | NULL | help@tesco.com | 81.3 | 354000 | 4.6 | 1078.5 |
| 5 | MS005 | Aldi | Germany | EU | Discount | service@aldi.com | 72.8 | 230000 | 4.3 | 1151.3 |
| 6 | MS006 | Lidl | Germany | Europe | discount | info@lidl.com | 132.4 | 250000 | 4.4 | 1283.7 |
| 7 | MS007 | 7Eleven | Japan | Asia | Convenience | support@7eleven.com | 89.2 | 45000 | 5.0 | 1372.9 |
| 8 | MS008 | Target | USA | NA | Hypermarket | target.com | 107.4 | 440000 | 4.2 | 1480.3 |
| 9 | MS009 | RelianceMart | India | APAC | Supermarket | care@reliance.com | 38.5 | 0 | 4.0 | 1518.8 |
| 10 | MS010 | DMart | India | Asia | Super Market | support@dmart.com | 19.8 | 75000 | 4.9 | 1538.6 |
| 11 | MS011 | Woolworths | Australia | ANZ | Supermarket | contact@woolworths.com | 43.2 | 200000 | 4.5 | 1581.8 |
| 12 | MS012 | Coles | Australia | Australia | Supermarket | sales@@coles.com | 41.8 | 120000 | 4.6 | 1623.6 |
| 13 | MS013 | Metro | Canada | NA | Grocery | metro@metro.ca | 21.4 | 97000 | 4.4 | 1645.0 |
| 14 | MS014 | Edeka | Germany | EU | Retail | info@edeka | 78.3 | 405000 | 4.3 | 1723.3 |
| 15 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 | 1742.0 |
| 16 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 | 1760.7 |
| 17 | MS016 | BigBazaar | India | Asia | Unknown | help@bigbazaar.com | 15.2 | 65000 | 3.9 | 1775.9 |
| 18 | MS017 | Kroger | USA | north america | Hypermarket | support@kroger.com | 150.8 | 420000 | 4.4 | 1926.7 |
| 19 | MS018 | Migros | Switzerland | EU | Hyper Market | migros@migros.com | 34.7 | 106000 | 4.5 | 1961.4 |
| 20 | MS019 | AEON | Japan | Asia | NULL | service@aeon.co.jp | 82.1 | 560000 | 4.6 | 2043.5 |
Explanation
This
example uses RETAIN to calculate a cumulative sales total across all
observations. The variable Total_Sales is initialized to zero only once and
then automatically accumulates each store's annual sales as the DATA step
progresses. This technique is widely used for cumulative revenue, inventory
balances, cash flow calculations, and operational performance tracking. Because
the retained variable persists across iterations, each record contains the
total sales observed up to that point, enabling straightforward trend analysis
without requiring additional procedures.
6. Using NMISS() and CMISS()
Enterprise datasets often contain incomplete records. SAS provides specialized functions to quantify missing values.
/*----------------------------------------------------------
Count missing values
-----------------------------------------------------------*/
data missing_summary;
set marketstores_raw;
Numeric_Missing=
nmiss(Annual_Sales_Million,Employees,Store_Rating);
Character_Missing=cmiss(Store_ID,Store_Name,
Country,Region,Category,Contact_Email);
run;
proc print data=missing_summary;
run;
OUTPUT:
| Obs | Store_ID | Store_Name | Country | Region | Category | Contact_Email | Annual_Sales_Million | Employees | Store_Rating | Numeric_Missing | Character_Missing |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | MS001 | Walmart | USA | NA | Hypermarket | support@walmart.com | 648.1 | 2100000 | 4.8 | 0 | 0 |
| 2 | MS002 | Costco | usa | North America | Wholesale | contactcostco.com | 254.4 | 316000 | 4.7 | 0 | 0 |
| 3 | MS003 | Carrefour | France | EU | HyperMarket | info@carrefour.com | 94.7 | 321000 | 4.5 | 0 | 0 |
| 4 | MS004 | Tesco | UK | Europe | NULL | help@tesco.com | 81.3 | 354000 | 4.6 | 0 | 0 |
| 5 | MS005 | Aldi | Germany | EU | Discount | service@aldi.com | -72.8 | 230000 | 4.3 | 0 | 0 |
| 6 | MS006 | Lidl | Germany | Europe | discount | info@lidl.com | 132.4 | -250000 | 4.4 | 0 | 0 |
| 7 | MS007 | 7Eleven | Japan | Asia | Convenience | support@7eleven.com | 89.2 | 45000 | 5.8 | 0 | 0 |
| 8 | MS008 | Target | USA | NA | Hypermarket | target.com | 107.4 | 440000 | 4.2 | 0 | 0 |
| 9 | MS009 | RelianceMart | India | APAC | Supermarket | care@reliance.com | 38.5 | . | 4.0 | 1 | 0 |
| 10 | MS010 | DMart | India | Asia | Super Market | support@dmart.com | 19.8 | 75000 | 4.9 | 0 | 0 |
| 11 | MS011 | Woolworths | Australia | ANZ | Supermarket | contact@woolworths.com | 43.2 | 200000 | 4.5 | 0 | 0 |
| 12 | MS012 | Coles | Australia | Australia | Supermarket | sales@@coles.com | 41.8 | 120000 | 4.6 | 0 | 0 |
| 13 | MS013 | Metro | Canada | NA | Grocery | metro@metro.ca | 21.4 | 97000 | 4.4 | 0 | 0 |
| 14 | MS014 | Edeka | Germany | EU | Retail | info@edeka | 78.3 | 405000 | 4.3 | 0 | 0 |
| 15 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 | 0 | 0 |
| 16 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 | 0 | 0 |
| 17 | MS016 | BigBazaar | India | Asia | Unknown | help@bigbazaar.com | 15.2 | 65000 | 3.9 | 0 | 0 |
| 18 | MS017 | Kroger | USA | north america | Hypermarket | support@kroger.com | 150.8 | 420000 | 4.4 | 0 | 0 |
| 19 | MS018 | Migros | Switzerland | EU | Hyper Market | migros@migros.com | 34.7 | 106000 | 4.5 | 0 | 0 |
| 20 | MS019 | AEON | Japan | Asia | NULL | service@aeon.co.jp | 82.1 | 560000 | 4.6 | 0 | 0 |
Explanation
NMISS()
counts missing numeric values, while CMISS() counts missing values across both
character and numeric arguments. These functions are especially valuable during
initial data profiling because they quickly identify incomplete observations
that may affect downstream analyses. In regulated environments, records with
excessive missing information are often routed for manual review or excluded
according to predefined business rules. Rather than writing numerous individual
IF MISSING() checks, these functions provide a concise, efficient, and scalable
way to measure data completeness.
7. Using ABS() to Correct Invalid Numeric Values
Negative operational metrics are often impossible and require correction.
/*----------------------------------------------------------
Convert negative values to positive
-----------------------------------------------------------*/
data abs_demo;
set marketstores_raw;
Annual_Sales_Million=abs(Annual_Sales_Million);
Employees=abs(Employees);
run;
proc print data=abs_demo;
run;
OUTPUT:
| Obs | Store_ID | Store_Name | Country | Region | Category | Contact_Email | Annual_Sales_Million | Employees | Store_Rating |
|---|---|---|---|---|---|---|---|---|---|
| 1 | MS001 | Walmart | USA | NA | Hypermarket | support@walmart.com | 648.1 | 2100000 | 4.8 |
| 2 | MS002 | Costco | usa | North America | Wholesale | contactcostco.com | 254.4 | 316000 | 4.7 |
| 3 | MS003 | Carrefour | France | EU | HyperMarket | info@carrefour.com | 94.7 | 321000 | 4.5 |
| 4 | MS004 | Tesco | UK | Europe | NULL | help@tesco.com | 81.3 | 354000 | 4.6 |
| 5 | MS005 | Aldi | Germany | EU | Discount | service@aldi.com | 72.8 | 230000 | 4.3 |
| 6 | MS006 | Lidl | Germany | Europe | discount | info@lidl.com | 132.4 | 250000 | 4.4 |
| 7 | MS007 | 7Eleven | Japan | Asia | Convenience | support@7eleven.com | 89.2 | 45000 | 5.8 |
| 8 | MS008 | Target | USA | NA | Hypermarket | target.com | 107.4 | 440000 | 4.2 |
| 9 | MS009 | RelianceMart | India | APAC | Supermarket | care@reliance.com | 38.5 | . | 4.0 |
| 10 | MS010 | DMart | India | Asia | Super Market | support@dmart.com | 19.8 | 75000 | 4.9 |
| 11 | MS011 | Woolworths | Australia | ANZ | Supermarket | contact@woolworths.com | 43.2 | 200000 | 4.5 |
| 12 | MS012 | Coles | Australia | Australia | Supermarket | sales@@coles.com | 41.8 | 120000 | 4.6 |
| 13 | MS013 | Metro | Canada | NA | Grocery | metro@metro.ca | 21.4 | 97000 | 4.4 |
| 14 | MS014 | Edeka | Germany | EU | Retail | info@edeka | 78.3 | 405000 | 4.3 |
| 15 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 |
| 16 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 |
| 17 | MS016 | BigBazaar | India | Asia | Unknown | help@bigbazaar.com | 15.2 | 65000 | 3.9 |
| 18 | MS017 | Kroger | USA | north america | Hypermarket | support@kroger.com | 150.8 | 420000 | 4.4 |
| 19 | MS018 | Migros | Switzerland | EU | Hyper Market | migros@migros.com | 34.7 | 106000 | 4.5 |
| 20 | MS019 | AEON | Japan | Asia | NULL | service@aeon.co.jp | 82.1 | 560000 | 4.6 |
Explanation
The ABS()
function returns the absolute value of a numeric variable, effectively removing
any negative sign. In this retail example, negative annual sales and employee
counts represent data-entry errors rather than legitimate business values.
Applying ABS() standardizes these fields before analytical processing,
preventing distorted summary statistics and misleading dashboards. While this
approach is appropriate for demonstration purposes, production systems should
first investigate why negative values occurred and determine whether
correction, exclusion, or manual verification is the correct business action.
8. Using ROUND() for Standardized Numeric Precision
Analytical datasets should maintain consistent decimal precision.
/*----------------------------------------------------------
Standardize decimal precision
-----------------------------------------------------------*/
data rounded_sales;
set abs_demo;
Annual_Sales_Million=round(Annual_Sales_Million,0.01);
Store_Rating=round(Store_Rating,0.1);
run;
proc print data=rounded_sales;
run;
OUTPUT:
| Obs | Store_ID | Store_Name | Country | Region | Category | Contact_Email | Annual_Sales_Million | Employees | Store_Rating |
|---|---|---|---|---|---|---|---|---|---|
| 1 | MS001 | Walmart | USA | NA | Hypermarket | support@walmart.com | 648.1 | 2100000 | 4.8 |
| 2 | MS002 | Costco | usa | North America | Wholesale | contactcostco.com | 254.4 | 316000 | 4.7 |
| 3 | MS003 | Carrefour | France | EU | HyperMarket | info@carrefour.com | 94.7 | 321000 | 4.5 |
| 4 | MS004 | Tesco | UK | Europe | NULL | help@tesco.com | 81.3 | 354000 | 4.6 |
| 5 | MS005 | Aldi | Germany | EU | Discount | service@aldi.com | 72.8 | 230000 | 4.3 |
| 6 | MS006 | Lidl | Germany | Europe | discount | info@lidl.com | 132.4 | 250000 | 4.4 |
| 7 | MS007 | 7Eleven | Japan | Asia | Convenience | support@7eleven.com | 89.2 | 45000 | 5.8 |
| 8 | MS008 | Target | USA | NA | Hypermarket | target.com | 107.4 | 440000 | 4.2 |
| 9 | MS009 | RelianceMart | India | APAC | Supermarket | care@reliance.com | 38.5 | . | 4.0 |
| 10 | MS010 | DMart | India | Asia | Super Market | support@dmart.com | 19.8 | 75000 | 4.9 |
| 11 | MS011 | Woolworths | Australia | ANZ | Supermarket | contact@woolworths.com | 43.2 | 200000 | 4.5 |
| 12 | MS012 | Coles | Australia | Australia | Supermarket | sales@@coles.com | 41.8 | 120000 | 4.6 |
| 13 | MS013 | Metro | Canada | NA | Grocery | metro@metro.ca | 21.4 | 97000 | 4.4 |
| 14 | MS014 | Edeka | Germany | EU | Retail | info@edeka | 78.3 | 405000 | 4.3 |
| 15 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 |
| 16 | MS015 | Spar | Austria | Europe | Retail | contact@spar.com | 18.7 | 91000 | 4.2 |
| 17 | MS016 | BigBazaar | India | Asia | Unknown | help@bigbazaar.com | 15.2 | 65000 | 3.9 |
| 18 | MS017 | Kroger | USA | north america | Hypermarket | support@kroger.com | 150.8 | 420000 | 4.4 |
| 19 | MS018 | Migros | Switzerland | EU | Hyper Market | migros@migros.com | 34.7 | 106000 | 4.5 |
| 20 | MS019 | AEON | Japan | Asia | NULL | service@aeon.co.jp | 82.1 | 560000 | 4.6 |
Explanation
The ROUND()
function ensures numeric values follow a consistent level of precision. In this
example, annual sales are rounded to two decimal places, while customer ratings
are rounded to one decimal place. Consistent precision improves report
readability, reduces minor floating-point discrepancies, and supports
reproducible statistical analyses. Standardized numeric formatting is
particularly important when datasets feed executive dashboards, financial
reports, or machine learning models, where inconsistent decimal precision can
complicate comparisons and validation efforts.
Key Takeaways
|
SAS
Feature |
Enterprise
Use Case |
|
ARRAY |
Apply
the same logic to multiple variables efficiently |
|
DO Loop |
Automate
repetitive processing tasks |
|
Nested DO
Loop |
Generate
repeated validation, simulation, or reporting records |
|
RETAIN |
Preserve
values across observations for counters and cumulative metrics |
|
NMISS() |
Count
missing numeric values quickly |
|
CMISS() |
Measure
overall record completeness |
|
ABS() |
Correct
invalid negative numeric values |
|
ROUND() |
Standardize
numeric precision for reporting and analysis |
9.Raw R Dataset (Intentional Errors Included)
library(tibble)
marketstores_raw <- tribble(
~Store_ID,~Store_Name,~Country,~Region,~Category,~Annual_Sales_Million,~Employees,~Store_Rating,~Contact_Email,
"MS001","Walmart","USA","NA","Hypermarket",648.1,2100000,4.8,"support@walmart.com",
"MS002","Costco","usa","North America","Wholesale",254.4,316000,4.7,"contactcostco.com",
"MS003"," Carrefour","France","EU","HyperMarket",94.7,321000,4.5,"info@carrefour.com",
"MS004","Tesco","UK","Europe","NULL",81.3,354000,4.6,"help@tesco.com",
"MS005","Aldi","Germany","EU","Discount",-72.8,230000,4.3,"service@aldi.com",
"MS006","Lidl"," Germany","Europe","discount",132.4,-250000,4.4,"info@lidl.com",
"MS007","7Eleven","Japan","Asia","Convenience",89.2,45000,5.8,"support@7eleven.com",
"MS008","Target","USA","NA","Hypermarket",107.4,440000,4.2,"target.com",
"MS009","RelianceMart","India","APAC","Supermarket",38.5,NA,4.0,"care@reliance.com",
"MS010","DMart","India","Asia","Super Market",19.8,75000,4.9,"support@dmart.com",
"MS011","Woolworths","Australia","ANZ","Supermarket",43.2,200000,4.5,"contact@woolworths.com",
"MS012","Coles","Australia","Australia","Supermarket",41.8,120000,4.6,"sales@@coles.com",
"MS013","Metro","Canada","NA"," Grocery ",21.4,97000,4.4,"metro@metro.ca",
"MS014","Edeka","Germany","EU","Retail",78.3,405000,4.3,"info@edeka",
"MS015","Spar","Austria","Europe","Retail",18.7,91000,4.2,"contact@spar.com",
"MS015","Spar","Austria","Europe","Retail",18.7,91000,4.2,"contact@spar.com",
"MS016","BigBazaar","India","Asia","Unknown",15.2,65000,3.9,"help@bigbazaar.com",
"MS017","Kroger","USA","north america","Hypermarket",150.8,420000,4.4,"support@kroger.com",
"MS018","Migros","Switzerland","EU","Hyper Market",34.7,106000,4.5,"migros@migros.com",
"MS019","AEON","Japan","Asia","NULL",82.1,560000,4.6,"service@aeon.co.jp"
)
OUTPUT:
|
Store_ID |
Store_Name |
Country |
Region |
Category |
Annual_Sales_Million |
Employees |
Store_Rating |
Contact_Email |
|
MS001 |
Walmart |
USA |
NA |
Hypermarket |
648.1 |
2100000 |
4.8 |
support@walmart.com |
|
MS002 |
Costco |
usa |
North
America |
Wholesale |
254.4 |
316000 |
4.7 |
contactcostco.com |
|
MS003 |
Carrefour |
France |
EU |
HyperMarket |
94.7 |
321000 |
4.5 |
info@carrefour.com |
|
MS004 |
Tesco |
UK |
Europe |
NULL |
81.3 |
354000 |
4.6 |
help@tesco.com |
|
MS005 |
Aldi |
Germany |
EU |
Discount |
-72.8 |
230000 |
4.3 |
service@aldi.com |
|
MS006 |
Lidl |
Germany |
Europe |
discount |
132.4 |
-250000 |
4.4 |
info@lidl.com |
|
MS007 |
7Eleven |
Japan |
Asia |
Convenience |
89.2 |
45000 |
5.8 |
support@7eleven.com |
|
MS008 |
Target |
USA |
NA |
Hypermarket |
107.4 |
440000 |
4.2 |
target.com |
|
MS009 |
RelianceMart |
India |
APAC |
Supermarket |
38.5 |
4 |
care@reliance.com |
|
|
MS010 |
DMart |
India |
Asia |
Super
Market |
19.8 |
75000 |
4.9 |
support@dmart.com |
|
MS011 |
Woolworths |
Australia |
ANZ |
Supermarket |
43.2 |
200000 |
4.5 |
contact@woolworths.com |
|
MS012 |
Coles |
Australia |
Australia |
Supermarket |
41.8 |
120000 |
4.6 |
sales@@coles.com |
|
MS013 |
Metro |
Canada |
NA |
Grocery |
21.4 |
97000 |
4.4 |
metro@metro.ca |
|
MS014 |
Edeka |
Germany |
EU |
Retail |
78.3 |
405000 |
4.3 |
info@edeka |
|
MS015 |
Spar |
Austria |
Europe |
Retail |
18.7 |
91000 |
4.2 |
contact@spar.com |
|
MS015 |
Spar |
Austria |
Europe |
Retail |
18.7 |
91000 |
4.2 |
contact@spar.com |
|
MS016 |
BigBazaar |
India |
Asia |
Unknown |
15.2 |
65000 |
3.9 |
help@bigbazaar.com |
|
MS017 |
Kroger |
USA |
north
america |
Hypermarket |
150.8 |
420000 |
4.4 |
support@kroger.com |
|
MS018 |
Migros |
Switzerland |
EU |
Hyper
Market |
34.7 |
106000 |
4.5 |
migros@migros.com |
|
MS019 |
AEON |
Japan |
Asia |
NULL |
82.1 |
560000 |
4.6 |
service@aeon.co.jp |
Explanation of the R Dataset (Key Points)
The R
dataset mirrors the same operational issues introduced in the SAS version,
making it ideal for comparing equivalent cleaning techniques across both
platforms. Missing values are represented using NA, while inconsistent strings,
duplicate identifiers, malformed email formats, negative numeric values,
invalid ratings, and inconsistent categorical labels simulate realistic
enterprise data challenges. Unlike SAS, R dynamically manages character vectors
without predefined lengths, eliminating truncation concerns but still requiring
explicit string standardization. This parallel dataset enables one-to-one
comparisons between SAS DATA Step logic and modern tidyverse functions
throughout the remaining parts of the project.
Character Truncation Risk
in SAS vs R
One of
the most overlooked aspects of SAS programming is the placement of the LENGTH
statement. In SAS, the length of a character variable is determined when it is
first created. If a variable is assigned a short value before the LENGTH
statement, SAS permanently allocates that shorter length, potentially
truncating longer values that appear later. This silent truncation can break
joins, distort reports, and create duplicate records that are difficult to
detect. Therefore, experienced SAS programmers always declare LENGTH statements
before any assignments or conditional logic.
R behaves
differently. Character vectors automatically expand to accommodate longer
strings, so truncation is generally not a concern. However, inconsistent
capitalization, extra whitespace, and malformed text still require careful
cleaning to ensure reliable analyses.
10. Loading Required Packages
Before beginning the cleaning workflow, load the packages commonly used
in enterprise R projects.
library(tidyverse)
library(janitor)
library(stringr)
library(tidyr)
library(lubridate)
library(purrr)
Explanation
The tidyverse collection provides a cohesive framework for
data manipulation and analysis. dplyr
is used for data transformation, stringr
simplifies string processing, tidyr
reshapes datasets and manages missing values, janitor
standardizes variable names, lubridate
handles dates and times, and purrr
supports functional programming for repetitive tasks. Together, these packages
form a powerful toolkit for building clean, reproducible, and maintainable data
pipelines. In enterprise environments, loading these libraries at the start of
a project ensures consistent functionality and promotes standardized coding
practices across teams.
11. Cleaning Multiple Numeric Variables Using across()
The across() function allows the same transformation to be applied to
several columns simultaneously, serving a role similar to SAS arrays.
marketstores_clean <-
marketstores_raw %>%
mutate(
across(
c(Annual_Sales_Million,
Employees,
Store_Rating),
~replace_na(.,0)
)
) %>%
mutate(
Annual_Sales_Million = abs(Annual_Sales_Million),
Employees = abs(Employees),
Store_Rating = if_else(Store_Rating > 5, 5, Store_Rating)
)
OUTPUT:
|
Store_ID |
Store_Name |
Country |
Region |
Category |
Annual_Sales_Million |
Employees |
Store_Rating |
Contact_Email |
|
MS001 |
Walmart |
USA |
NA |
Hypermarket |
648.1 |
2100000 |
4.8 |
support@walmart.com |
|
MS002 |
Costco |
usa |
North
America |
Wholesale |
254.4 |
316000 |
4.7 |
contactcostco.com |
|
MS003 |
Carrefour |
France |
EU |
HyperMarket |
94.7 |
321000 |
4.5 |
info@carrefour.com |
|
MS004 |
Tesco |
UK |
Europe |
NULL |
81.3 |
354000 |
4.6 |
help@tesco.com |
|
MS005 |
Aldi |
Germany |
EU |
Discount |
72.8 |
230000 |
4.3 |
service@aldi.com |
|
MS006 |
Lidl |
Germany |
Europe |
discount |
132.4 |
250000 |
4.4 |
info@lidl.com |
|
MS007 |
7Eleven |
Japan |
Asia |
Convenience |
89.2 |
45000 |
5 |
support@7eleven.com |
|
MS008 |
Target |
USA |
NA |
Hypermarket |
107.4 |
440000 |
4.2 |
target.com |
|
MS009 |
RelianceMart |
India |
APAC |
Supermarket |
38.5 |
0 |
4 |
care@reliance.com |
|
MS010 |
DMart |
India |
Asia |
Super
Market |
19.8 |
75000 |
4.9 |
support@dmart.com |
|
MS011 |
Woolworths |
Australia |
ANZ |
Supermarket |
43.2 |
200000 |
4.5 |
contact@woolworths.com |
|
MS012 |
Coles |
Australia |
Australia |
Supermarket |
41.8 |
120000 |
4.6 |
sales@@coles.com |
|
MS013 |
Metro |
Canada |
NA |
Grocery |
21.4 |
97000 |
4.4 |
metro@metro.ca |
|
MS014 |
Edeka |
Germany |
EU |
Retail |
78.3 |
405000 |
4.3 |
info@edeka |
|
MS015 |
Spar |
Austria |
Europe |
Retail |
18.7 |
91000 |
4.2 |
contact@spar.com |
|
MS015 |
Spar |
Austria |
Europe |
Retail |
18.7 |
91000 |
4.2 |
contact@spar.com |
|
MS016 |
BigBazaar |
India |
Asia |
Unknown |
15.2 |
65000 |
3.9 |
help@bigbazaar.com |
|
MS017 |
Kroger |
USA |
north
america |
Hypermarket |
150.8 |
420000 |
4.4 |
support@kroger.com |
|
MS018 |
Migros |
Switzerland |
EU |
Hyper
Market |
34.7 |
106000 |
4.5 |
migros@migros.com |
|
MS019 |
AEON |
Japan |
Asia |
NULL |
82.1 |
560000 |
4.6 |
service@aeon.co.jp |
Explanation
The across() function applies
a common transformation to multiple columns without duplicating code. Here,
missing numeric values are replaced with zero using replace_na().
The abs() function converts
negative sales and employee counts into positive values, while if_else()
restricts customer ratings to a maximum of five. This approach is concise,
highly readable, and easy to extend when additional numeric variables require
identical cleaning rules. Compared with SAS arrays, across()
provides a declarative, vectorized syntax that operates efficiently on entire
columns rather than iterating through individual observations.
12. Using a for Loop for Repetitive Operations
Although vectorized operations are generally preferred in R,
for loops remain useful for custom iterative logic.
numeric_cols <-
c("Annual_Sales_Million",
"Employees",
"Store_Rating")
for(col in numeric_cols){
marketstores_clean[[col]] <-
replace_na(marketstores_clean[[col]],0)
}
OUTPUT:
|
Store_ID |
Store_Name |
Country |
Region |
Category |
Annual_Sales_Million |
Employees |
Store_Rating |
Contact_Email |
|
MS001 |
Walmart |
USA |
NA |
Hypermarket |
648.1 |
2100000 |
4.8 |
support@walmart.com |
|
MS002 |
Costco |
usa |
North
America |
Wholesale |
254.4 |
316000 |
4.7 |
contactcostco.com |
|
MS003 |
Carrefour |
France |
EU |
HyperMarket |
94.7 |
321000 |
4.5 |
info@carrefour.com |
|
MS004 |
Tesco |
UK |
Europe |
NULL |
81.3 |
354000 |
4.6 |
help@tesco.com |
|
MS005 |
Aldi |
Germany |
EU |
Discount |
72.8 |
230000 |
4.3 |
service@aldi.com |
|
MS006 |
Lidl |
Germany |
Europe |
discount |
132.4 |
250000 |
4.4 |
info@lidl.com |
|
MS007 |
7Eleven |
Japan |
Asia |
Convenience |
89.2 |
45000 |
5 |
support@7eleven.com |
|
MS008 |
Target |
USA |
NA |
Hypermarket |
107.4 |
440000 |
4.2 |
target.com |
|
MS009 |
RelianceMart |
India |
APAC |
Supermarket |
38.5 |
0 |
4 |
care@reliance.com |
|
MS010 |
DMart |
India |
Asia |
Super
Market |
19.8 |
75000 |
4.9 |
support@dmart.com |
|
MS011 |
Woolworths |
Australia |
ANZ |
Supermarket |
43.2 |
200000 |
4.5 |
contact@woolworths.com |
|
MS012 |
Coles |
Australia |
Australia |
Supermarket |
41.8 |
120000 |
4.6 |
sales@@coles.com |
|
MS013 |
Metro |
Canada |
NA |
Grocery |
21.4 |
97000 |
4.4 |
metro@metro.ca |
|
MS014 |
Edeka |
Germany |
EU |
Retail |
78.3 |
405000 |
4.3 |
info@edeka |
|
MS015 |
Spar |
Austria |
Europe |
Retail |
18.7 |
91000 |
4.2 |
contact@spar.com |
|
MS015 |
Spar |
Austria |
Europe |
Retail |
18.7 |
91000 |
4.2 |
contact@spar.com |
|
MS016 |
BigBazaar |
India |
Asia |
Unknown |
15.2 |
65000 |
3.9 |
help@bigbazaar.com |
|
MS017 |
Kroger |
USA |
north
america |
Hypermarket |
150.8 |
420000 |
4.4 |
support@kroger.com |
|
MS018 |
Migros |
Switzerland |
EU |
Hyper
Market |
34.7 |
106000 |
4.5 |
migros@migros.com |
|
MS019 |
AEON |
Japan |
Asia |
NULL |
82.1 |
560000 |
4.6 |
service@aeon.co.jp |
Explanation
This example demonstrates a traditional for
loop that iterates over a list of numeric column names and replaces missing
values with zero. While tidyverse functions often eliminate the need for
explicit loops, for loops remain
valuable when transformations involve complex conditional logic, interactions
with external systems, or dynamically generated variables. The loop processes
each specified column individually, improving flexibility without hardcoding
repetitive statements. Understanding both vectorized operations and iterative
programming enables R developers to choose the most appropriate solution for
different production scenarios.
13. Creating Running Totals with cumsum()
Running totals are frequently required for financial reporting and executive dashboards.
marketstores_running <-
marketstores_clean %>%
arrange(Store_ID) %>%
mutate(Total_Sales =
cumsum(Annual_Sales_Million)
)
OUTPUT:
|
Store_ID |
Store_Name |
Country |
Region |
Category |
Annual_Sales_Million |
Employees |
Store_Rating |
Contact_Email |
Total_Sales |
|
MS001 |
Walmart |
USA |
NA |
Hypermarket |
648.1 |
2100000 |
4.8 |
support@walmart.com |
648.1 |
|
MS002 |
Costco |
usa |
North
America |
Wholesale |
254.4 |
316000 |
4.7 |
contactcostco.com |
902.5 |
|
MS003 |
Carrefour |
France |
EU |
HyperMarket |
94.7 |
321000 |
4.5 |
info@carrefour.com |
997.2 |
|
MS004 |
Tesco |
UK |
Europe |
NULL |
81.3 |
354000 |
4.6 |
help@tesco.com |
1078.5 |
|
MS005 |
Aldi |
Germany |
EU |
Discount |
72.8 |
230000 |
4.3 |
service@aldi.com |
1151.3 |
|
MS006 |
Lidl |
Germany |
Europe |
discount |
132.4 |
250000 |
4.4 |
info@lidl.com |
1283.7 |
|
MS007 |
7Eleven |
Japan |
Asia |
Convenience |
89.2 |
45000 |
5 |
support@7eleven.com |
1372.9 |
|
MS008 |
Target |
USA |
NA |
Hypermarket |
107.4 |
440000 |
4.2 |
target.com |
1480.3 |
|
MS009 |
RelianceMart |
India |
APAC |
Supermarket |
38.5 |
0 |
4 |
care@reliance.com |
1518.8 |
|
MS010 |
DMart |
India |
Asia |
Super
Market |
19.8 |
75000 |
4.9 |
support@dmart.com |
1538.6 |
|
MS011 |
Woolworths |
Australia |
ANZ |
Supermarket |
43.2 |
200000 |
4.5 |
contact@woolworths.com |
1581.8 |
|
MS012 |
Coles |
Australia |
Australia |
Supermarket |
41.8 |
120000 |
4.6 |
sales@@coles.com |
1623.6 |
|
MS013 |
Metro |
Canada |
NA |
Grocery |
21.4 |
97000 |
4.4 |
metro@metro.ca |
1645 |
|
MS014 |
Edeka |
Germany |
EU |
Retail |
78.3 |
405000 |
4.3 |
info@edeka |
1723.3 |
|
MS015 |
Spar |
Austria |
Europe |
Retail |
18.7 |
91000 |
4.2 |
contact@spar.com |
1742 |
|
MS015 |
Spar |
Austria |
Europe |
Retail |
18.7 |
91000 |
4.2 |
contact@spar.com |
1760.7 |
|
MS016 |
BigBazaar |
India |
Asia |
Unknown |
15.2 |
65000 |
3.9 |
help@bigbazaar.com |
1775.9 |
|
MS017 |
Kroger |
USA |
north
america |
Hypermarket |
150.8 |
420000 |
4.4 |
support@kroger.com |
1926.7 |
|
MS018 |
Migros |
Switzerland |
EU |
Hyper
Market |
34.7 |
106000 |
4.5 |
migros@migros.com |
1961.4 |
|
MS019 |
AEON |
Japan |
Asia |
NULL |
82.1 |
560000 |
4.6 |
service@aeon.co.jp |
2043.5 |
Explanation
The cumsum() function
calculates cumulative sums across observations, producing a running total of
annual sales. Before computing the cumulative values, the dataset is sorted by Store_ID
to ensure a consistent calculation order. Running totals are widely used in
enterprise reporting to monitor revenue growth, inventory movement, expenditure
tracking, and operational performance over time. In SAS, similar functionality
is often implemented using the RETAIN
statement, whereas R provides the built-in cumsum()
function, making cumulative calculations straightforward and efficient.
SAS vs R Comparison
|
SAS |
R (tidyverse) |
Purpose |
|
|
|
Apply the same transformation to multiple
variables |
|
|
|
Perform iterative operations |
|
|
|
Create cumulative values |
|
|
|
Count missing numeric values |
|
|
|
Count missing values across columns |
|
|
|
Convert negative values to positive |
|
|
|
Standardize numeric precision |
|
|
|
Replace missing values with defaults |
Key Takeaways
· across()
provides a concise, scalable alternative to SAS arrays for applying
transformations across multiple columns.
· for
loops remain useful for custom iterative tasks, although vectorized
operations are generally preferred in R.
· cumsum()
offers a simple way to compute running totals, serving a role similar to SAS's RETAIN.
· coalesce(),
replace_na(), and is.na()
simplify missing-value handling and improve data consistency.
· abs()
and round() help enforce
logical numeric values and standardized precision, supporting accurate
reporting and downstream analytics.
SAS vs R Comparison and Interview-Oriented
Guide,Choosing DATA Step, PROC SQL, and tidyverse
Enterprise organizations rarely rely on a single programming paradigm for
all data-processing tasks. Experienced Clinical SAS Programmers, Statistical
Programmers, Data Engineers, and Data Scientists understand that selecting the
appropriate tool is as important as writing correct code. SAS DATA Step, PROC
SQL, and the R tidyverse each have distinct strengths, and choosing the right
one improves performance, readability, maintainability, and auditability.
In production environments such as clinical trials, retail analytics,
banking, insurance, and healthcare, developers often combine these technologies
to create robust, scalable, and compliant data pipelines. Understanding when to
use each approach is a common interview topic because it reflects practical
experience rather than theoretical knowledge.
SAS DATA Step vs PROC SQL vs R tidyverse
|
Feature |
SAS DATA Step |
PROC SQL |
R tidyverse |
|
Primary Purpose |
Row-by-row data manipulation |
Relational data operations |
Modern data transformation and analysis |
|
Processing Style |
Sequential |
Set-based |
Vectorized |
|
Speed for Complex Data Cleaning |
Excellent |
Moderate |
Excellent |
|
Joining Multiple Tables |
Good using |
Excellent using |
Excellent using |
|
Handling Missing Values |
Excellent |
Good |
Excellent |
|
Conditional Programming |
Very Strong |
Moderate |
Very Strong |
|
Arrays / Bulk Processing |
Supported |
Not Supported |
|
|
Running Totals |
|
Window functions (limited) |
|
|
Sorting Requirement |
Often Required |
Usually Not |
Not Required for joins |
|
Readability |
Excellent for transformations |
Excellent for queries |
Excellent for analytics |
|
Regulatory Acceptance |
Industry Standard |
Industry Standard |
Growing rapidly |
|
Clinical Trial Usage |
Very High |
High |
Moderate to High |
|
Banking & Finance |
Very High |
High |
Very High |
|
Machine Learning Integration |
Limited |
Limited |
Excellent |
|
Visualization |
Basic |
Basic |
Advanced |
When Should You Use the SAS DATA Step?
The DATA Step is the foundation of SAS programming and is optimized for
sequential observation-by-observation processing. It excels when records need
to be validated, standardized, transformed, or derived using business rules.
Best Use Cases
· Cleaning
operational datasets
· Creating
analysis-ready datasets
· Variable
derivation
· Missing
value correction
· Date
calculations
· Character
standardization
· Array
processing
· Running
totals
· FIRST./LAST.
processing
· Production
ETL workflows
Example Scenario
Suppose a retail company receives one million daily store records. Each
record requires:
· Removing
leading spaces
· Standardizing
country names
· Correcting
invalid ratings
· Replacing
negative sales
· Deriving
business flags
· Creating
audit variables
These transformations are naturally expressed using the DATA Step because
each record is processed independently.
Advantages of DATA Step
1.Extremely fast
for sequential processing
2.Easy to debug
3.Highly readable
4.Excellent for
enterprise ETL
5.Supports arrays
6.Supports RETAIN
7.Supports
FIRST./LAST.
8.Supports custom
business logic
9.Preferred in
regulated environments
Limitations of DATA Step
· Complex
joins require sorting.
· SQL-style
summarization is less intuitive.
· Multiple-table
relationships require additional programming.
· Nested
merge logic can become difficult to maintain.
When Should You Use PROC SQL?
PROC SQL is designed for relational database operations. It works best when
combining datasets, summarizing information, and querying large relational
structures.
Best Use Cases
· Joining
datasets
· Removing
duplicates
· Aggregation
· Summary
reporting
· Lookup
tables
· Subqueries
· Creating
views
· Database
integration
Advantages of PROC SQL
1.Excellent for
joins
2.Easy aggregation
3.Compact code
4.Database
compatibility
5.Supports
subqueries
6.Easy duplicate
detection
7.Ideal for
reporting
Limitations of PROC SQL
· Not
optimized for row-by-row transformations.
· Arrays
cannot be used.
· FIRST./LAST.
processing is unavailable.
· Complex
business-rule validation can become difficult to read.
When Should You Use R tidyverse?
The tidyverse is a collection of R packages designed for expressive and
efficient data manipulation. It emphasizes readable pipelines and vectorized
operations.
Best Use Cases
· Exploratory
Data Analysis
· Machine
Learning
· Data
Visualization
· Feature
Engineering
· Text
Processing
· Data
Wrangling
· Interactive
Analytics
· Dashboard
Preparation
Example Scenario
Suppose marketing analysts need to:
· Filter
premium stores
· Group
by region
· Calculate
average sales
· Standardize
text
· Produce
visualizations
· Build
predictive models
The tidyverse workflow allows these operations to be chained together using %>%,
producing concise and readable code.
Advantages of tidyverse
1.Very readable
syntax
2. Excellent
visualization support
3. Strong machine
learning ecosystem
4.Easy handling of
missing values
5.Powerful string
manipulation
6.Rich package
ecosystem
7.Functional
programming with purrr
8.Interactive
analytics
Limitations of tidyverse
· Requires
package management.
· Less
standardized than SAS in highly regulated industries.
· Large
datasets may require additional optimization.
· Audit
trails often require custom implementation.
Enterprise Decision Guide
|
Business Requirement |
Best Choice |
|
Clean messy operational data |
DATA Step |
|
Standardize variables |
DATA Step |
|
Remove duplicates |
PROC SQL or PROC SORT |
|
Merge patient datasets |
DATA Step or PROC SQL |
|
Join multiple lookup tables |
PROC SQL |
|
Create analysis dataset |
DATA Step |
|
Produce summary statistics |
PROC SQL |
|
Build dashboards |
R tidyverse |
|
Machine learning |
R tidyverse |
|
Interactive visualization |
R tidyverse |
|
Clinical SDTM mapping |
DATA Step |
|
ADaM derivations |
DATA Step |
|
Executive reporting |
PROC SQL + PROC REPORT |
|
Text analytics |
tidyverse |
|
Data exploration |
tidyverse |
Enterprise Workflow Example
A pharmaceutical company receives raw clinical trial data from multiple
research sites.
Step 1 — DATA Step
· Standardize
dates
· Correct
missing values
· Validate
subject identifiers
· Create
derived variables
· Remove
invalid observations
↓
Step 2 — PROC SQL
· Merge
demographics
· Join
adverse events
· Join
laboratory results
· Generate
summary datasets
↓
Step 3 — R tidyverse
· Explore
trends
· Create
visualizations
· Perform
statistical modeling
· Generate
publication-ready graphics
↓
Step 4
Validated analytical datasets are delivered to statisticians for regulatory
reporting and submission.
Key Takeaways
· SAS
DATA Step is the preferred tool for record-level cleaning,
derivations, and enterprise ETL workflows.
· PROC
SQL excels at joins, aggregations, relational queries, and reporting.
· R
tidyverse provides a modern, expressive framework for data wrangling,
visualization, and advanced analytics.
· In
enterprise environments, these tools are complementary rather than competing.
Selecting the appropriate approach based on the task results in cleaner code,
better performance, stronger auditability, and more maintainable data
pipelines.
Interview Discussion: DATA Step vs PROC SQL
Question: Why not use PROC SQL for everything?
Answer:
Although PROC SQL is excellent for joins and summarization, it is not
optimized for observation-by-observation transformations. DATA Step provides
greater flexibility for implementing business rules, handling arrays, using
retained variables, processing groups with FIRST./LAST.,
and performing complex conditional logic. In production ETL workflows, DATA
Step often delivers better readability and maintainability for data cleaning
tasks.
Interview Discussion: DATA Step vs tidyverse
Question: Why do pharmaceutical companies still use SAS
when R is available?
Answer:
SAS has a long history of regulatory acceptance in industries such as
pharmaceuticals. It provides stable, validated procedures, detailed logging,
and reproducible outputs that align with compliance requirements. R, however,
excels in advanced analytics, visualization, and machine learning. Many
organizations therefore adopt a hybrid approach, using SAS for validated
production pipelines and R for exploratory analysis and advanced modeling.
Interview Discussion: PROC SQL vs tidyverse
Question: Both perform joins. Which should you choose?
Answer:
PROC SQL is preferred within SAS-based production environments because it
integrates seamlessly with SAS datasets and enterprise databases. The tidyverse
provides highly readable join functions such as left_join(),
inner_join(), and full_join(),
making it ideal for analytics workflows in R. The choice depends on the
surrounding ecosystem, regulatory expectations, and downstream processing
requirements.
Interview Questions
Q1. When would you prefer the DATA Step over
PROC SQL?
Answer: When performing row-level transformations, deriving
variables, cleaning data, using arrays, applying complex conditional logic, or
processing grouped observations with FIRST.
and LAST. variables.
Q2. Why is PROC SQL preferred for joins?
Answer: PROC SQL supports multiple join types, aggregation,
grouping, subqueries, and relational operations in a concise syntax without
requiring explicit merge logic.
Q3. When is R tidyverse a better option?
Answer: When performing exploratory data analysis, creating
visualizations, building predictive models, processing text, or developing
interactive analytical workflows.
Q4. Which tool is fastest for large-scale
data cleaning?
Answer: For sequential, row-level transformations, the SAS
DATA Step is generally the preferred choice in enterprise environments. For
relational operations, PROC SQL is efficient, while tidyverse offers excellent
performance for vectorized transformations and analytics within R.
Q5. Do companies use SAS or R?
Answer: Many organizations use both. A common enterprise
pattern is to use SAS for validated ETL pipelines, SDTM/ADaM derivations, and
regulatory reporting, while using R for advanced analytics, statistical modeling,
machine learning, and visualization.
Comments
Post a Comment