在 SAS 中查找并替换来自单独 table 的值
Look up and replace values from a separate table in SAS
数据集 HAVE
包含两个名称拼写错误的变量:names
和 friends
。
Name Age Friend
Jon 11 Ann
Jon 11 Tom
Jimb 12 Egg
Joe 11 Egg
Joe 11 Anne
Joe 11 Tom
Jed 10 Ann
我有一个小数据集 CORRECTIONS
,其中包括 wrong_names
和 resolved_names
。
current_names resolved_names
Jon John
Ann Anne
Jimb Jim
我需要 names
或 friends
中的任何名称与 CORRECTIONS
的 wrong_names
列中的名称匹配才能重新编码到resolved_name
中对应的字符串。生成的数据集 WANT
应如下所示:
Name Age Friend
John 11 Anne
John 11 Tom
Jim 12 Egg
Joe 11 Egg
Joe 11 Anne
Joe 11 Tom
Jed 10 Anne
在 R 中,我可以使用 if_else()
简单地调用每个数据帧和向量,但 SAS 中的 DATA 步骤不能很好地处理多个数据集。我如何使用 CORRECTIONS
作为查找来进行这些替换 table?
试试这个:
proc sql;
create table want as
select p.name,p.age,
case
when q.current_names is null then p.friend
else q.resolved_names
end
as friend1
from
(
select
case
when b.current_names is null then a.name
else b.resolved_names
end
as name,
a.age,a.friend
from
have a
left join
corrections b
on upcase(a.name) = upcase(b.current_names)
) p
left join
corrections q
on upcase(p.friend) = upcase(q.current_names);
quit;
输出:
name age friend
John 11 Anne
Jed 10 Anne
Joe 11 Anne
Jim 12 Egg
Joe 11 Egg
Joe 11 Tom
John 11 Tom
如有任何说明,请告诉我。
您可以在 proc sql
中使用 UPDATE
:
proc sql ;
update have a
set name = (select resolved_names b from corrections where a.name = b.current_names)
where name in(select current_names from corrections)
;
update have a
set friend = (select resolved_names b from corrections where a.friend = b.current_names)
where friend in(select current_names from corrections)
;
quit ;
或者,您可以使用以下格式:
/* Create format */
data current_fmt ;
retain fmtname 'NAMEFIX' type 'C' ;
set resolved_names ;
start = current_names ;
label = resolved_names ;
run ;
proc format cntlin=current_fmt ; run ;
/* Apply format */
data want ;
set have ;
name = put(name ,$NAMEFIX.) ;
friend = put(friend,$NAMEFIX.) ;
run ;
在 SAS 中有很多方法可以进行查找。
但是,首先,我建议对您的查找进行重复数据删除 table(例如,使用 PROC SORT 和数据 Step/Set/By)- 决定保留哪些重复项(如果任何存在)。
至于查找任务本身,为了简单和学习,我建议如下:
"OLD SCHOOL" 方式 - 适用于审核输入和输出(当输入 table 符合要求的顺序时更容易验证连接的结果):
*** data to validate;
data have;
length name . age 4. friend .;
input name age friend;
datalines;
Jon 11 Ann
Jon 11 Tom
Jimb 12 Egg
Joe 11 Egg
Joe 11 Anne
Joe 11 Tom
Jed 10 Ann
run;
*** lookup table;
data corrections;
length current_names . resolved_names .;
input current_names resolved_names;
datalines;
Jon John
Ann Anne
Jimb Jim
run;
*** de-duplicate lookup table;
proc sort data=corrections nodupkey; by current_names; run;
proc sort data=have; by name; run;
data have_corrected;
merge have(in=a)
corrections(in=b rename=(current_names=name))
;
by name;
if a;
if b then do;
name=resolved_names;
end;
run;
SQL 方式 - 避免对 have table:
进行排序
proc sql;
create table have_corrected_sql as
select
coalesce(b.resolved_names, a.name) as name,
a.age,
a.friend
from work.have as a left join work.corrections as b
on a.name eq b.current_names
order by name;
quit;
注意 Coalesce() 用于用 table
中的名称替换缺失的 resolved_names 值(即没有更正时)
编辑:为了反映 Quentin 的(正确)评论,即我错过了对姓名和朋友字段的更新。
基于更正 2 个字段,同样有很多方法,但本质是仅当查找中存在值时才更新值(更正)table。散列对象在这方面非常擅长,一旦您理解了它的声明。
注意:Hash 对象中的任何关键字段都需要在 Length 语句中预先指定。
编辑:根据 ChrisJ 对 Length 语句声明的替代方案,以及我的回复(见下文)- 最好声明在声明散列 table 之前需要定义关键变量。
data have_corrected;
keep name age friend;
length current_names .;
*** load valid names into hash lookup table;
if _n_=1 then do;
declare hash h(dataset: 'work.corrections');
rc = h.defineKey('current_names');
rc = h.defineData('resolved_names');
rc = h.defineDone();
end;
do until(eof);
set have(in=a) end=eof;
*** validate both name fields;
if h.find(key:name) eq 0 then
name = resolved_names;
if h.find(key:friend) eq 0 then
friend = resolved_names;
output;
end;
run;
编辑:回答有关 ChrisJ 的 SQL/Update 替代方案的评论
基本上,您需要将每个 UPDATE 语句限制为仅在更正中具有 name 值或 friend 值的那些行 table - 这是通过在指定 set var 之后添加另一个 where 子句来完成的=(子句)。见下文。
注意。 AFAIK,满足您要求的 SQL 解决方案将需要对基础 table 和查找 table 进行 1 次以上的传递。
然而,lookup/hash table 需要单次传递基础 table,加载查找 table,然后是查找操作本身。您可以在日志中看到性能差异...
proc sql;
*** create copy of have table;
create table work.have_sql as select * from work.have;
*** correct name field;
update work.have_sql as u
set name = (select resolved_names
from work.corrections as n
where u.name=n.current_names)
where u.name in (select current_names from work.corrections)
;
*** correct friend field;
update work.have_sql as u
set friend = (select resolved_names
from work.corrections as n
where u.friend=n.current_names)
where u.friend in (select current_names from work.corrections)
;
quit;
给定数据
*** data to validate;
data have;
length name . age 4. friend .;
input name age friend;
datalines;
Jon 11 Ann
Jon 11 Tom
Jimb 12 Egg
Joe 11 Egg
Joe 11 Anne
Joe 11 Tom
Jed 10 Ann
run;
*** lookup table;
data corrections;
length from_name . to_name .;
input from_name to_name;
datalines;
Jon John
Ann Anne
Jimb Jim
run;
一个 SQL 替代方法是对要映射的每个字段执行现有映射 select 查找。这与为每个要映射的字段加入更正 table 一次是相反的。
proc sql;
create table want1 as
select
case when exists (select * from corrections where from_name=name)
then (select to_name from corrections where from_name=name)
else name
end as name
, age
, case when exists (select * from corrections where from_name=friend)
then (select to_name from corrections where from_name=friend)
else friend
end as friend
from
have
;
执行内联左联接的另一种 SAS 唯一方法是使用自定义格式。
data cntlin;
set corrections;
retain fmtname '$cohen'; /* the fixer */
rename from_name=start to_name=label;
run;
proc format cntlin=cntlin;
run;
data want2;
set have;
name = put(name,$cohen.);
friend = put(friend,$cohen.);
run;
数据集 HAVE
包含两个名称拼写错误的变量:names
和 friends
。
Name Age Friend
Jon 11 Ann
Jon 11 Tom
Jimb 12 Egg
Joe 11 Egg
Joe 11 Anne
Joe 11 Tom
Jed 10 Ann
我有一个小数据集 CORRECTIONS
,其中包括 wrong_names
和 resolved_names
。
current_names resolved_names
Jon John
Ann Anne
Jimb Jim
我需要 names
或 friends
中的任何名称与 CORRECTIONS
的 wrong_names
列中的名称匹配才能重新编码到resolved_name
中对应的字符串。生成的数据集 WANT
应如下所示:
Name Age Friend
John 11 Anne
John 11 Tom
Jim 12 Egg
Joe 11 Egg
Joe 11 Anne
Joe 11 Tom
Jed 10 Anne
在 R 中,我可以使用 if_else()
简单地调用每个数据帧和向量,但 SAS 中的 DATA 步骤不能很好地处理多个数据集。我如何使用 CORRECTIONS
作为查找来进行这些替换 table?
试试这个:
proc sql;
create table want as
select p.name,p.age,
case
when q.current_names is null then p.friend
else q.resolved_names
end
as friend1
from
(
select
case
when b.current_names is null then a.name
else b.resolved_names
end
as name,
a.age,a.friend
from
have a
left join
corrections b
on upcase(a.name) = upcase(b.current_names)
) p
left join
corrections q
on upcase(p.friend) = upcase(q.current_names);
quit;
输出:
name age friend
John 11 Anne
Jed 10 Anne
Joe 11 Anne
Jim 12 Egg
Joe 11 Egg
Joe 11 Tom
John 11 Tom
如有任何说明,请告诉我。
您可以在 proc sql
中使用 UPDATE
:
proc sql ; update have a set name = (select resolved_names b from corrections where a.name = b.current_names) where name in(select current_names from corrections) ; update have a set friend = (select resolved_names b from corrections where a.friend = b.current_names) where friend in(select current_names from corrections) ; quit ;
或者,您可以使用以下格式:
/* Create format */ data current_fmt ; retain fmtname 'NAMEFIX' type 'C' ; set resolved_names ; start = current_names ; label = resolved_names ; run ; proc format cntlin=current_fmt ; run ; /* Apply format */ data want ; set have ; name = put(name ,$NAMEFIX.) ; friend = put(friend,$NAMEFIX.) ; run ;
在 SAS 中有很多方法可以进行查找。
但是,首先,我建议对您的查找进行重复数据删除 table(例如,使用 PROC SORT 和数据 Step/Set/By)- 决定保留哪些重复项(如果任何存在)。
至于查找任务本身,为了简单和学习,我建议如下:
"OLD SCHOOL" 方式 - 适用于审核输入和输出(当输入 table 符合要求的顺序时更容易验证连接的结果):
*** data to validate;
data have;
length name . age 4. friend .;
input name age friend;
datalines;
Jon 11 Ann
Jon 11 Tom
Jimb 12 Egg
Joe 11 Egg
Joe 11 Anne
Joe 11 Tom
Jed 10 Ann
run;
*** lookup table;
data corrections;
length current_names . resolved_names .;
input current_names resolved_names;
datalines;
Jon John
Ann Anne
Jimb Jim
run;
*** de-duplicate lookup table;
proc sort data=corrections nodupkey; by current_names; run;
proc sort data=have; by name; run;
data have_corrected;
merge have(in=a)
corrections(in=b rename=(current_names=name))
;
by name;
if a;
if b then do;
name=resolved_names;
end;
run;
SQL 方式 - 避免对 have table:
进行排序proc sql;
create table have_corrected_sql as
select
coalesce(b.resolved_names, a.name) as name,
a.age,
a.friend
from work.have as a left join work.corrections as b
on a.name eq b.current_names
order by name;
quit;
注意 Coalesce() 用于用 table
中的名称替换缺失的 resolved_names 值(即没有更正时)编辑:为了反映 Quentin 的(正确)评论,即我错过了对姓名和朋友字段的更新。
基于更正 2 个字段,同样有很多方法,但本质是仅当查找中存在值时才更新值(更正)table。散列对象在这方面非常擅长,一旦您理解了它的声明。
注意:Hash 对象中的任何关键字段都需要在 Length 语句中预先指定。
编辑:根据 ChrisJ 对 Length 语句声明的替代方案,以及我的回复(见下文)- 最好声明在声明散列 table 之前需要定义关键变量。
data have_corrected;
keep name age friend;
length current_names .;
*** load valid names into hash lookup table;
if _n_=1 then do;
declare hash h(dataset: 'work.corrections');
rc = h.defineKey('current_names');
rc = h.defineData('resolved_names');
rc = h.defineDone();
end;
do until(eof);
set have(in=a) end=eof;
*** validate both name fields;
if h.find(key:name) eq 0 then
name = resolved_names;
if h.find(key:friend) eq 0 then
friend = resolved_names;
output;
end;
run;
编辑:回答有关 ChrisJ 的 SQL/Update 替代方案的评论
基本上,您需要将每个 UPDATE 语句限制为仅在更正中具有 name 值或 friend 值的那些行 table - 这是通过在指定 set var 之后添加另一个 where 子句来完成的=(子句)。见下文。
注意。 AFAIK,满足您要求的 SQL 解决方案将需要对基础 table 和查找 table 进行 1 次以上的传递。
然而,lookup/hash table 需要单次传递基础 table,加载查找 table,然后是查找操作本身。您可以在日志中看到性能差异...
proc sql;
*** create copy of have table;
create table work.have_sql as select * from work.have;
*** correct name field;
update work.have_sql as u
set name = (select resolved_names
from work.corrections as n
where u.name=n.current_names)
where u.name in (select current_names from work.corrections)
;
*** correct friend field;
update work.have_sql as u
set friend = (select resolved_names
from work.corrections as n
where u.friend=n.current_names)
where u.friend in (select current_names from work.corrections)
;
quit;
给定数据
*** data to validate;
data have;
length name . age 4. friend .;
input name age friend;
datalines;
Jon 11 Ann
Jon 11 Tom
Jimb 12 Egg
Joe 11 Egg
Joe 11 Anne
Joe 11 Tom
Jed 10 Ann
run;
*** lookup table;
data corrections;
length from_name . to_name .;
input from_name to_name;
datalines;
Jon John
Ann Anne
Jimb Jim
run;
一个 SQL 替代方法是对要映射的每个字段执行现有映射 select 查找。这与为每个要映射的字段加入更正 table 一次是相反的。
proc sql;
create table want1 as
select
case when exists (select * from corrections where from_name=name)
then (select to_name from corrections where from_name=name)
else name
end as name
, age
, case when exists (select * from corrections where from_name=friend)
then (select to_name from corrections where from_name=friend)
else friend
end as friend
from
have
;
执行内联左联接的另一种 SAS 唯一方法是使用自定义格式。
data cntlin;
set corrections;
retain fmtname '$cohen'; /* the fixer */
rename from_name=start to_name=label;
run;
proc format cntlin=cntlin;
run;
data want2;
set have;
name = put(name,$cohen.);
friend = put(friend,$cohen.);
run;