SAS Hash 详解
想摄影不想学摄影
2022年07月26日 23:09
收录于文集
共44篇

一. 概述:

代码块
JavaScript
自动换行
复制代码
DATA work.claims_providers;
	LENGTH provider_lname provider_fname $25 npi $10;
	
	DECLARE HASH provider(dataset:'eiw.providers');
	provider.DEFINEKEY ('provider_id');
	provider.DEFINEDATA('provider_lname', 'provider_fname','npi');
	provider.DEFINEDONE();

	DO UNTIL (eof_claims);
		SET eiw.claims END = eof_claims;
		rc = provider.FIND();
		IF rc ne 0 THEN DO;
			provider_lname = 'Provider Not Found';
			provider_fname = 'Provider Not Found';
			npi = 'xxxxxxxxxx';
		END;
		OUTPUT;
	END;
	STOP;
RUN;
复制成功


二. 散列表

1. 定义

    散列表(Hash table,也叫哈希表),是根据 键(Key)直接访问在 内存存储位置 的 数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

https://www.zhihu.com/question/20820286

    Hash算法也被称为散列算法,Hash算法虽然被称为算法,但实际上它更像是一种思想。Hash算法没有一个固定的公式,只要符合散列思想的算法都可以被称为是Hash算法。

    若结构中存在和关键字K相等的记录,则必定在f(K)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系 f 为 散列函数(Hash function),按这个事先建立的表为散列表。

    给定表M,存在函数 f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

2. Hash Tables:

  • 散列表 是一个数据结构

  • 哈希表 存储在 内存,而不是磁盘

  • 哈希表可以被认为是一种 数组

  • 哈希表  将 键映射到值。

  • 键到值 的映射,通过哈希函数。

哈希表,简单来说就是一张带索引和存储空间的表,对于任意可哈希对象,通过哈希索引的计算公式:hash(hashable)%k(对可哈希对象进行哈希计算,然后对结果进行取余运算),可将该对象映射为0到k-1之间的某个表索引,然后在该索引所对应的空间进行变量的存储/读取等操。

3. 类比Python 字典:

(1)   python的字典 数据类型 是基于 hash 散列算法实现的。采用键值对(key:value)的形式。根据键(key)的值 计算 value 的地址,从而存取value。

    字典: 是集合类型,不是序列类型。字典通过key来获取value。

(2) python中字典的创建与访问:

    创建一个空字典: 

        d = {}

        d = dict()

    创建一个有初始值的字典:

        d = {'name&#​39;:"good&#​34;,'pv&#​39;:500000}

        d = dict(name = 'good&#​39;, pv = 500000)

    访问字典:

        d['name&#​39;]

python字典 运用hash的原理:

  1. 插入:对键进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的表地址空间为空,将键值对存入该地址空间;

  2. 更新:对键进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的地址空间中健与要更新的健一致,那么就更新该健所对应的值;

  3. 查询:对要查找的健进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的地址空间中健与要查询的健一致,那么就将该键值对取出来;

  4. 扩容:字典初始化的时候,会对应初始化一个有k个空间的表,等空间不够用的时候,系统就会自动扩容,这时候会对已经存在的键值对重新进行哈希取余运算(重新进行插入操作)保存到其它位置;

  5. 碰撞:有时候对于不同的键,经过哈希取余运算之后,得到的索引值一样,这时候怎么办?这时采用公开寻址的方式,运用固定的模式将键值对插入到其它的地址空间,比如线性寻址:如果第i个位置已经被使用,我们就看看第i+1个,第i+2个,第i+3个有没有被使用…直到找到一个空间或者对空间进行扩容。

比如:我们想存储 {’小小‘:18}这个 键值对,经过哈希和取余运算之后,我们发现,其对应的索引值是0,但是0所指向的空间已经被’小王‘占用了,这就是碰撞。怎么办呢?我们看看0+1对应的所以有没有被占用,如果没有,我们就把’小小‘放在索引1所对应的地址空间中。取的时候,也按照同样的规则,进行探查。

https://blog.csdn.net/qq_42815145/article/details/91353624


三. SAS Hash Objects

1. 作用 与 结构

    The SAS hash object is an incredibly powerful technique for integrating data from two or more datasets based on a common key.

The SAS hash object provide a very fast way to look up data from one dataset based on a common key linking records in another dataset.

优点:

  1. hash table 可以根据 K-V 定位数据,直接得到变量的存储地址,可以减少查询的次数;

  2. hash table的变量查找是在内存中进行的,可以提高性能;

  3. hash table可以在data步运行时的动态地添加、更新、删除等操作;

  4. hash 可以做一些merge 和 proc sql 难以实现的数据集合并,并在细节上可以有更多的控制。

A hash record consists of two parts: a key part and a data part.

  • The key part consists of one or more character and numeric values.

  • he data part consists of zero or more character and numeric values.

概述:

  1. Hash Tables are in memory data structures. 

  2. Hash Tables can be used for lookups, sorting, merging and to facilitate complex data manipulations by removing the disk I/O associated with frequent query and update statements. 

  3. Hash Tables can provide significant performance gains in certain circumstances. 

  4. Hash tables are implemented in SAS as objects and provide a wide range of functionality through their methods and properties.

2.  SAS中HASH 对象的创建:

  1. Declare the hash object. 定义hash对象

  2. Create an instance of (instantiate) the hash object.创建一个实例

  3. Initialize lookup keys and data.初始化 key 和 变量。

3.  建成SAS Hash 对象后 可以进行的操作:

  1. Store and retrieve data.存储和检索数据

  2. Maintain key summaries.维护key

  3. Replace and remove data.替换或移除数据

  4. Compare hash objects.比较hash对象

  5. Output a data set that contains the data in the hash object.输出一个数据集,该数据集包含 hash object中的数据。

代码块
JavaScript
自动换行
复制代码
DATA work.claims_providers;
	LENGTH provider_lname provider_fname $25 npi $10;
	
	DECLARE HASH provider(dataset:'eiw.providers');
	provider.DEFINEKEY ('provider_id');
	provider.DEFINEDATA('provider_lname', 'provider_fname','npi');
	provider.DEFINEDONE();

	DO UNTIL (eof_claims);
		SET eiw.claims END = eof_claims;
		rc = provider.FIND();
		IF rc ne 0 THEN DO;
			provider_lname = 'Provider Not Found';
			provider_fname = 'Provider Not Found';
			npi = 'xxxxxxxxxx';
		END;
		OUTPUT;
	END;
	STOP;
RUN;


1. 首先,定义一个hash,名称为"provider" , 将数据集"eiw.provider"读入hash表。

2. 使用 DOT notation 来定义hash的组件:
	定义 key 的变量为  数据集"eiw.provider" 的变量:'provider_id';
	定义hash value 对应的变量为	'provider_lname', 'provider_fname','npi' ;
3. definedone()表示 hash表定义完成。

4. 然后使用循环,对被查找的数据集 "eiw.claims" 进行循环,直到数据集最后一条观测。
	使用hash对象的 find方法,返回值为0 ,表示: 未找到; 返回值为非0 ,表示查找到。

复制成功

解析:

上面的程序中使用了语句:

代码块
JavaScript
自动换行
复制代码
LENGTH provider_lname provider_fname $25 npi $10;
复制成功

这是因为从hash对象返回 value 需要在PDV中有一个已分配的地址(location)来保留这个value。

补充知识:PDV

    The program data vector, abbreviated as PDV, is a logical concept to help you think about the manipulation of variables in the DATA step. 

    Prior to version 6 it was also a physical concept referring to the specific area in memory where DATA step variables were stored. 

    In version 6 the memory structure was reorganized to speed up the system time spent outside the ILDS [implied loop of the data step], but the PDV concept continues to be a good way for the programmer to think about what is important to him in variable manipulation. 

    The PDV can be thought of as one long list of storage areas for all the named variables of the DATA step.

PDV 可以被认为是DATA步中用来存放 已命名变量 的 一个地址列表。

网页链接​

the variables defined as the KEY and DATA components of the hash object are not automatically recognized by the DATA step as are those in a dataset declared in a SET statement. 

举例,如果不使用 length 进行声明 ,则报错:

这里如果不使用 length 语句,程序就会报错。

Hash 是一个运行时(running)组件,hash表只有在data步运行的时候才会被创建。如果不使用length 语句提前声明变量 “provider_lname, provider_fname, npi”, 则在程序执行时,PDV中并没有这几个变量的地址(location),所以,程序会报错。

因为hash 是running component, 所以, 在编译阶段,编译器不会识别 hash内部的定义(诸如:key,value),所以在运行阶段, PDV中并没有 definekey和definevalue中定义的变量,所以,在执行h.find()方法时,PDV中并没有对应变量值的location。

所以,虽然,被用来定义Hash 的数据集“'eiw.providers&#​39;”内部有变量 provider_lname、 provider_fname、 npi ,但编译器无法查看hash内部的信息,所以在编译阶段并不知道这些信息,而在定义hash前,就需要申明这几个变量。

4.  Hash对象的三个基本要点:

  1. 要放入内存的表。

  2. 用来通过hash函数 建立与内存对应存储地址的 KEY 值 (可以是数值、字符、或两者的混合)。

  3. 要调入内存中 与 key值 一起构成数组的变量。

SAS实现上面的三个要点:

  1. 发布HASH对象        

  2. 指定Key值变量

  3. 指定要加载那些数据变量

  4. 执行 object.definedone()。说明hash表已经在内存中准备好了。

其中, call missing(key,variable_1) 避免log中出现数据未初始化的提醒。

5.  定义hash对象的 步骤:

代码块
JavaScript
自动换行
复制代码
length d $20;
length k $20;

if _N_ = 1 then do;
	declare hash h();
	rc = h.defineKey('k');
	rc = h.defineData('d');
	rc = h.defineDone();
end;
复制成功

因为: 在 hash object 中 定义的 Key 和 Data 并不会自动的被添加到PDV, 所以需要首先使用 声明 并 初始化 key 和 data 变量,使得保留在PDV当中。 

因为: hash的定义只需要定义一次就行了, 所以使用了  if _n_ = 1 then do ; 语句。

https://www.sas.com/content/dam/SAS/en_ca/User%20Group%20Presentations/Regina-User-Group/ShaunKaufmann-IntroHash-Sept2013.pdf

declare语句的选项:

6.  SAS hash 对象的 方法:

  1. rc = obj.defineKey('key_var1&#​39;, …, 'key_varN&#​39;); Defines a set of hash object keys given by key_var1…key_varN. 

  2. rc = obj.defineData('data_var1&#​39;, …, 'data_varN&#​39;); Defines data, given by data_var1…data_varN, to be stored in the hash object. 

  3. rc = obj.defineDone();Indicates that key and data definitions are complete.

  4. rc = obj.add(); rc = obj.add(key: key_val1,…, key: key_valN, data: data_val1,…, data: data_valN); Adds the specified data associated with the given key to the hash object. 将给定的键值对(key-value) 添加到 hash object中。

  5. rc = obj.find(); rc = obj.find(key: key_val1, …, key: key_valN); Determines whether the given key has been stored in the hash object. If it has, the data variables are updated and the return code is set to zero. If the key is not found, the return code is non-zero.查找给定的key 是否在 hash object 中存在,如果存在,返回非0; 如果不存在,返回0 .这里“给定的key” 其实是用一个数据集中的 对应key 变量。

  6. rc = obj.replace(); rc = obj.replace(key: key_val1,…, key: key_valN, data: data_val1, …, data: data_valN);Replaces the data associated with the given key with new data as specified in data_val1…data_valN.替换 给定key 对应的data 的value。

  7. rc = obj.check();rc = obj.check(key: key_val1, …, key: key_valN);Checks whether the given key has been stored in the hash object. The data variables are not updated. Return codes are the same as for find.查看 给定的key 是否 在hash object 中存在。

  8. rc = obj.remove();rc = obj.remove(key: key_val1, …, key: key_valN); Removes the data associated with the given key.移除给定key 对应的data值。

  9. rc = obj.clear();Removes all entries from a hash object without deleting the hash object.清除整个hash object 的值,但不删除hash object。

  10. rc = obj.output(dataset: 'dataset_name&#​39;);Creates dataset dataset_name which will contain the data in the hash object.

  11. rc = obj.sum(sum: sum_var);rc = obj.sum(key: key_val1, …, key: key_valN, sum: sum_var);Gets the key summary for the given key and stores it in the DATA Step variable sum_var. Key summaries are incremented when a key is accessed.计算基本统计量。

  12. rc = obj.ref();rc = obj.ref(key: key_val1, …, key: key_valN);Performs a find operation for the current key. If the key is not in the hash object, it will be added.  ref 方法是 check 和 add 方法的整合(consolidates )。

  13. rc = obj.equals(hash: 'hash_obj&#​39;, result: res_var);Determines if two hash objects are equal. If they are equal, res_var is set to 1, otherwise it is set to zero.判断两个hash objects 是否相等。如果相等,结果变量:res_var 赋值为1,否则为0.

  14. rc = obj.delete();Deletes the hash object.删除hash 对象。

7.  Hash Attributes

  1. i = obj.num_items; Retrieves the number of elements in the hash object. hash对象中元素的数量。

  2. sz = obj.item_size; Obtains the item size, in bytes, for an item in the hash object.hash对象中,每个item的大小。

7. Hash 指针

  1. Gives you the ability to traverse your memory table from start to end, or vice versa. 

  2. A hash iterator is associated with a specific hash object and operates only on that hash object. 

  3. Before you declare your iterator you must declare your hash object.

Hash指针 方法:

  1. declare hiter iterobj('hash_obj&#​39;); Creates a hash iterator to retrieve items from the hash object named hash_obj. 

  2. rc = iterobj.first(); Copies the data for the first item in the hash object into the data variables for the hash object. 

  3. rc = iterobj.last(); Copies the data for the last item in the hash object into the data variables for the hash object.


举例:

例1. 

CALL MISSING : 将缺失值 赋值给 指定的变量。

这里,使用call missing, 是为了不让在log中打印多余的Note,例如,如果不适用call missing 语句,log中会打印:

代码块
JavaScript
自动换行
复制代码
NOTE: Variable provider_lname is uninitialized.
NOTE: Variable provider_fname is uninitialized.
NOTE: Variable npi is uninitialized.
复制成功

代码:

代码块
JavaScript
自动换行
复制代码
DATA work.claims_providers;
	LENGTH provider_lname provider_fname $25 npi $10 ;
	
	DECLARE hash provider(dataset:'eiw.providers');
	
	provider.DEFINEKEY ('provider_id');
	provider.DEFINEDATA('provider_lname', 'provider_fname','npi');
	provider.DEFINEDONE();
	
	CALL MISSING(provider_lname, provider_fname, npi);
	
	DO UNTIL (eof_claims);
		SET eiw.claims END = eof_claims;
		rc = provider.FIND();
		OUTPUT;
	END;
	STOP;
RUN;
复制成功

例2.

例1 中 ,使用了 length 语句来声明变量 provider_lname 、provider_fname、 npi 。

因为,如果不提前声明的话, 在编译阶段,不会在PDV中创建 这些变量,在程序后续的执行中,因无法在PDV中存放这些变量,导致程序出错。所以使用了length 语句提前声明了这些变量。

除了 使用length 解决这个问题,还可以使用:

代码块
JavaScript
自动换行
复制代码
IF _N_ = 0 THEN SET eiw.providers;
复制成功

因为_N_ 永远不会等于0, 所以后面的“ THEN SET eiw.providers; ” 永远不会被执行。但是,在编译阶段,编译器并不知道它不会被执行,所以还是需要编译这段代码,所以,会将数据集 eiw.providers的变量 在 PDV中分配一个位置。由此,实现了 和 例1 中 length 语句一样的作用。

这样还有一个好处,就是不再需要使用call missing 语句了。

代码块
JavaScript
自动换行
复制代码
DATA work.claims_providers;
	IF _N_ = 0 THEN 
		SET eiw.providers;
	
	DECLARE hash provider(dataset:'eiw.providers');
	
	provider.DEFINEKEY ('provider_id');
	provider.DEFINEDATA('provider_lname', 'provider_fname','npi');
	provider.DEFINEDONE();

	DO UNTIL (eof_claims);
		SET eiw.claims END = eof_claims;
		rc = provider.FIND();
		OUTPUT;
	END;
	STOP;
RUN;
复制成功

例3:

下面两段代码的区别:

  • 第一段代码有 " stop; " 语句。

  • 第二段代码没有 " stop;  &#​34; 语句,但有 “IF _N_ = 1 THEN DO;” 语句。

因为Data步, 当一次迭代结束后,指针会再次返回到Data步开始的位置继续下一次迭代。

但这里不需要重复进行迭代处理,所以使用了 stop; 语句,在一次迭代后 直接结束当前Data步。

Data步作用: Stops execution of the current DATA step.

第二段代码 使用了 “IF _N_ = 1 THEN DO;” 条件, 实现了和 stop statement 一样的效果。都是只在 在执行第一次迭代后 退出当前Data步。  

代码块
JavaScript
自动换行
复制代码
DATA work.claims_providers3b;
	IF _N_ = 0 THEN 
		SET eiw.providers;
	DECLARE hash
		provider(dataset:'eiw.providers');
		provider.DEFINEKEY ('provider_id');
		provider.DEFINEDATA('provider_lname',
			'provider_fname','npi');
		provider.DEFINEDONE();
	
	DO UNTIL (eof_claims);
		SET eiw.claims END = eof_claims;
		rc = provider.FIND();
		OUTPUT;
	END;
	STOP;
RUN;
复制成功

https://support.sas.com/resources/papers/proceedings15/3024-2015.pdf

代码块
JavaScript
自动换行
复制代码
DATA work.claims_providers3b;
	IF _N_ = 0 THEN 
		SET eiw.providers;
	
	IF _N_ = 1 THEN DO;
		DECLARE hash
			provider(dataset:'eiw.providers');
			provider.DEFINEKEY ('provider_id');
			provider.DEFINEDATA('provider_lname',
					'provider_fname','npi');
			provider.DEFINEDONE();
	END;
	
	DO UNTIL (eof_claims);
		SET eiw.claims END = eof_claims;
		rc1 = provider.FIND();
		OUTPUT;
	END;
RUN;
复制成功

例3:

手动加载hash对象

https://support.sas.com/resources/papers/proceedings15/3024-2015.pdf

代码块
JavaScript
自动换行
复制代码
DATA work.claims_providers;
	/*首先声明一个hash对象,这里并没有指定要加载到hash object 的 dataset.*/
	DECLARE hash provider();
		provider.DEFINEKEY ('provider_id');
		provider.DEFINEDATA('provider_lname', 'provider_fname','npi');
		provider.DEFINEDONE();
		
	/*这里使用循环,使用add()方法 手动将 eiw.providers 中的值加载到hash object.*/
	DO UNTIL (eof_providers);
		SET eiw.providers END = eof_providers;
		rc0 = provider.add();
	END;
	
	/*使用hash object 进行查询.*/
	DO UNTIL (eof_claims);
		SET work.claims END = eof_claims;
		rc1 = provider.FIND();
		OUTPUT;
	END;
	
RUN;
复制成功

这段代码,尽管没有使用: stop statement 和  "IF _N_ = 1&#​34; 条件,但是 "eiw.providers&#​34; 中的记录只读取了一次,因为当 _N_ = 2 的时候再次实例化hash object的时候,数据集不会被识别。

例4:

因为加载数据到hash object 是在 定义 hash object 的外部进行的,所以可以使用条件语句,对被加载到hash object 的观测进行更加精细的控制。例如使用where statement。

代码块
JavaScript
自动换行
复制代码
DATA work.claims_providers;

	/*声明一个hash object*/
	DECLARE hash provider();
		provider.DEFINEKEY ('provider_id');
		provider.DEFINEDATA('provider_lname', 'provider_fname','npi');
		provider.DEFINEDONE();
	
	/*使用where语句,对要加载到hash object 的观测 进行过滤.*/
	DO UNTIL (eof_providers);
		SET eiw.providers END = eof_providers;
		WHERE state IN ('TX','WI');
		rc0 = provider.add();
	END;
	
	/*在hash object 中查找指定key 的value 是否存在*/
	DO UNTIL (eof_claims);
		SET eiw.claims END = eof_claims;
		rc = provider.FIND();
		IF rc ne 0 THEN DO;
		provider_lname = 'Provider Not Found';
		provider_fname = 'Provider Not Found';
		npi = 'xxxxxxxxxx';
	END;
	OUTPUT;
	END;
RUN;
复制成功

https://support.sas.com/resources/papers/proceedings15/3024-2015.pdf


Summary:

例1.  Group Sums With the SAS Hash Object

对数据集,对 id  分组 进行 sum 。

代码块
JavaScript
自动换行
复制代码
data have;
input id $ x;
datalines;
002 2
003 5
003 1
001 5
002 9
001 6
001 3
002 4
003 7
;
run;


data _null_;

   /*首先,在data步迭代的第一次迭代 定义一个hash object*/
   dcl hash h();
   h.definekey('id');
   h.definedata('id', 's');
   h.definedone();
 
   /*对数据集have 进行循环*/
   do until (z);
      set have end = z;

      if h.find() ne 0 then s = 0;
      
	  s + x;
  	  /* 替换s的值*/
      h.replace(); 
   end;
 
   h.output(dataset : 'want');
/*   将hash表 输出到数据集 “want”*/
run;
复制成功

例2.  累积和 ( Cumulative Sums )

代码块
JavaScript
自动换行
复制代码
data want;
   if _N_ = 1 then do;
      dcl hash h();
      h.definekey('id');
      h.definedata('cs');
      h.definedone();
   end;
 
   set have;
 
   if h.find() then 
   		cs = x;
   else 
     	cs = sum(cs, x);
   h.replace();
run;
复制成功

例3.  结果同例2

代码块
JavaScript
自动换行
复制代码
data want;
   if _N_ = 1 then do;
      dcl hash h(suminc : 'x');
      h.definekey('id');
      h.definedone();
   end;
 
   set have;
 
   h.ref();
   h.sum(sum : cum_sum);
run;
复制成功

https://sasnrd.com/calculate-sum-hash-object-sas/