SAS:PROC SORT then first. and a retained counter, --SEQ
--SEQ is a within-subject row number after a defined order. In R: arrange(), then group_by(USUBJID), then row_number(). Without arrange(), the numbers follow the current row order, not the study keys.
Quick reference
library(dplyr)df |>arrange(USUBJID, TUEVAL, TULNKID, VISITNUM, TUDY) |># order firstgroup_by(USUBJID) |>mutate(SEQ =as.double(row_number())) |># --SEQ; double for SAS comparesungroup()
R
SAS
arrange(...) then group_by(USUBJID) then row_number()
PROC SORT then if first.usubjid then seq = 0; seq + 1;
group_by(USUBJID)
BY USUBJID
as.double(row_number())
SAS numeric is double
Traps: row_number() follows the current row order, so skip arrange() and --SEQ is whatever the last PROC left behind. Equal dates need tie-breakers in arrange(). _N_ is not a within-BY counter. Cast to double so waldo/diffdf match SAS numeric; XPT has no separate integer type.
Worked examples below.
The example table
Tumor results in input order, which is not the TUSEQ order.
library(dplyr)
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
STD-001-0001 has investigator lesions T01 and T02, and one independent-assessor row. VISITNUM 3 is the first input row. Sponsor order used here: evaluator, lesion, visit, study day.
Wrong: row_number() without arrange()
tu |>group_by(USUBJID) |>mutate(TUSEQ =row_number()) |>ungroup()
STD-001-0001 is numbered 1 to 4 in file order. VISITNUM 3 is TUSEQ 1. SAS BY usubjid on unsorted data stops with BY variables are not properly sorted. That check is only USUBJID, not the secondary keys. R does not error. This example is already in USUBJID order, so SAS would also number it in file order without an error.
Right: arrange(), then number
proc sort data=tu;
by usubjid tueval tulnkid visitnum tudy;
run;
data tu;
set tu;
by usubjid;
if first.usubjid then tuseq = 0;
tuseq + 1;
run;
tu |>arrange(USUBJID, TUEVAL, TULNKID, VISITNUM, TUDY) |>group_by(USUBJID) |>mutate(TUSEQ =as.double(row_number())) |>ungroup()
INDEPENDENT ASSESSOR sorts before INVESTIGATOR and is TUSEQ 1. Then investigator T01 visit 1, T01 visit 3, T02 visit 1. TULNKID precedes VISITNUM, so T01 visit 3 precedes T02 visit 1. STD-001-0002 restarts at 1.
_N_ does not restart within BY. The SAS analogue of group_by(USUBJID) then row_number() is the first. plus retained counter above, not _N_.
Importantrow_number() numbers the current order
It does not infer a clinical order. group_by(USUBJID) then row_number() uses the frame's current order. Sort first. Include every study key in arrange(), including tie-breakers.
Tie-breakers
Two rows can share a study day. arrange(USUBJID, TUDY) alone leaves their relative order undefined. Add keys that make a row unique, in the sponsor-defined order: TUEVAL, TULNKID, VISITNUM, TUDY. If a tie remains, add a source sequence or a datetime.
as.double() for R-side compares
XPT has numeric and character only. Integer is not a distinct XPT type. row_number() returns integer in R. SAS numeric is double. waldo and diffdf report that as a type difference even when the values match. Cast when you need the R objects to compare equal: