与 SAS 的比较#
对于可能来自 SAS 的用户,本页面旨在演示如何使用 pandas 执行不同的 SAS 操作。
如果你是 pandas 新手,你可能想先阅读 10 Minutes to pandas 来熟悉该库。
按照惯例,我们像这样导入 pandas 和 NumPy:
数据结构#
通用术语翻译#
pandas |
SAS |
|---|---|
|
data set |
column |
variable |
row |
observation |
groupby |
BY-group |
|
|
DataFrame#
pandas 中的 DataFrame 类似于 SAS 数据集——一个二维数据源,具有标记的列,这些列可以是不同类型的。正如本文档所示,使用 SAS 的 DATA 语句几乎可以应用的任何操作,也可以在 pandas 中完成。
Series#
Series 是代表 DataFrame 单列的数据结构。SAS 没有单独的数据结构来表示单列,但总体而言,处理 Series 类似于在 DATA 步中引用列。
Index#
每个 DataFrame 和 Series 都有一个 Index —— 这是数据*行*的标签。SAS 没有完全类似的概。数据集的行本质上是无标签的,除了可以在 DATA 步中访问的隐式整数索引 (_N_)。
在 pandas 中,如果没有指定索引,默认也会使用整数索引(第一行 = 0,第二行 = 1,依此类推)。虽然使用带标签的 Index 或 MultiIndex 可以实现复杂的分析,并且最终是理解 pandas 的重要组成部分,但在此比较中,我们将基本忽略 Index,仅将 DataFrame 视为列的集合。有关如何有效使用 Index 的更多信息,请参阅 indexing documentation 。
副本 vs. 原地操作#
大多数 pandas 操作会返回 Series/DataFrame 的副本。要使更改“生效”,您需要将其赋给一个新变量:
sorted_df = df.sort_values("col1")
或者覆盖原始变量:
df = df.sort_values("col1")
备注
您会看到一些方法提供了 inplace=True 或 copy=False 关键字参数:
df.replace(5, inplace=True)
目前正在积极讨论弃用和删除大多数方法的 inplace 和 copy``(例如,``dropna),只保留一小部分方法(包括 replace)。在 Copy-on-Write 的上下文中,这两个关键字将不再需要。该提案可以在 here 找到。
数据输入/输出#
从值构建 DataFrame#
SAS 数据集可以通过在 datalines 语句后放置数据并指定列名来构建。
data df;
input x y;
datalines;
1 2
3 4
5 6
;
run;
pandas DataFrame 可以通过多种方式构建,但对于少量值,通常将其指定为 Python 字典会很方便,其中键是列名,值是数据。
读取外部数据#
与 SAS 类似,pandas 提供了用于从多种格式读取数据的实用程序。pandas 测试中的 tips 数据集(csv )将在后面的许多示例中使用。
SAS 提供 PROC IMPORT 来将 csv 数据读取到数据集中。
proc import datafile='tips.csv' dbms=csv out=tips replace;
getnames=yes;
run;
pandas 的方法是 read_csv() ,其工作方式类似。
与 PROC IMPORT 类似,read_csv 可以接受许多参数来指定如何解析数据。例如,如果数据是制表符分隔的,并且没有列名,pandas 命令将是:
tips = pd.read_csv("tips.csv", sep="\t", header=None)
# alternatively, read_table is an alias to read_csv with tab delimiter
tips = pd.read_table("tips.csv", header=None)
除了文本/csv,pandas 还支持多种其他数据格式,如 Excel、HDF5 和 SQL 数据库。这些都可以通过 pd.read_* 函数读取。有关更多详细信息,请参阅 IO documentation 。
限制输出#
默认情况下,pandas 会截断大型 DataFrame 的输出,只显示第一行和最后一行。这可以通过 changing the pandas options 或使用 DataFrame.head() 或 DataFrame.tail() 来覆盖。
SAS 中的等效操作是:
proc print data=df(obs=5);
run;
导出数据#
SAS 中 PROC IMPORT 的对应操作是 PROC EXPORT。
proc export data=tips outfile='tips2.csv' dbms=csv;
run;
类似地,在 pandas 中,read_csv 的对应操作是 to_csv() ,其他数据格式也遵循类似的 API。
tips.to_csv("tips2.csv")
数据操作#
列上的操作#
在 DATA 步中,可以在新列或现有列上使用任意数学表达式。
data tips;
set tips;
total_bill = total_bill - 2;
new_bill = total_bill / 2;
run;
pandas 通过指定单个 Series 在 DataFrame 中提供矢量化操作。也可以用同样的方式分配新列。 DataFrame.drop() 方法会从 DataFrame 中删除一列。
过滤#
SAS 中的过滤是通过 if 或 where 语句对一个或多个列进行的。
data tips;
set tips;
if total_bill > 10;
run;
data tips;
set tips;
where total_bill > 10;
/* equivalent in this case - where happens before the
DATA step begins and can also be used in PROC statements */
run;
DataFrame 可以通过多种方式进行过滤;其中最直观的是使用 boolean indexing 。
上面的语句只是将一个包含 True/False 对象的 Series 传递给 DataFrame,返回所有 True 的行。
If/then 逻辑#
在 SAS 中,if/then 逻辑可用于创建新列。
data tips;
set tips;
format bucket $4.;
if total_bill < 10 then bucket = 'low';
else bucket = 'high';
run;
在 pandas 中,可以使用来自 numpy 的 where 方法完成相同的操作。
日期功能#
SAS 提供了多种函数来对日期/日期时间列执行操作。
data tips;
set tips;
format date1 date2 date1_plusmonth mmddyy10.;
date1 = mdy(1, 15, 2013);
date2 = mdy(2, 15, 2015);
date1_year = year(date1);
date2_month = month(date2);
* shift date to beginning of next interval;
date1_next = intnx('MONTH', date1, 1);
* count intervals between dates;
months_between = intck('MONTH', date1, date2);
run;
下面显示了 pandas 中的等效操作。除了这些函数之外,pandas 还支持 Base SAS 中没有的其他时间序列功能(例如重采样和自定义偏移量) - 有关更多详细信息,请参阅 timeseries documentation 。
选择列#
SAS 在 DATA 步骤中提供关键字来选择、删除和重命名列。
data tips;
set tips;
keep sex total_bill tip;
run;
data tips;
set tips;
drop sex;
run;
data tips;
set tips;
rename total_bill=total_bill_2;
run;
下面在 pandas 中显示了相同的操作。
保留特定列#
删除列#
重命名列#
按值排序#
SAS 中的排序是通过 PROC SORT 完成的
proc sort data=tips;
by sex total_bill;
run;
pandas 有一个 DataFrame.sort_values() 方法,它接受一个要排序的列列表。
字符串处理#
查找字符串长度#
SAS 使用 LENGTHN 和 LENGTHC 函数确定字符的长度。LENGTHN 排除尾随空格,LENGTHC 包含尾随空格。
data _null_;
set tips;
put(LENGTHN(time));
put(LENGTHC(time));
run;
您可以使用 Series.str.len() 查找字符串的长度。在 Python 3 中,所有字符串都是 Unicode 字符串。len 包含尾随空格。使用 len 和 rstrip 来排除尾随空格。
查找子字符串位置#
SAS 使用 FINDW 函数确定字符串中字符的位置。FINDW 接受由第一个参数定义的字符串,并搜索您作为第二个参数提供的子字符串的第一个位置。
data _null_;
set tips;
put(FINDW(sex,'ale'));
run;
您可以使用 Series.str.find() 方法查找字符串列中字符的位置。find 搜索子字符串的第一个位置。如果找到子字符串,则返回其位置。如果未找到,则返回 -1。请记住,Python 索引是从零开始的。
按位置提取子字符串#
SAS 使用 SUBSTR 函数根据位置从字符串中提取子字符串。
data _null_;
set tips;
put(substr(sex,1,1));
run;
使用 pandas,您可以使用 [] 符号通过位置从字符串中提取子字符串。请记住,Python 索引是从零开始的。
提取第 n 个单词#
SAS 的 SCAN 函数返回字符串中的第 n 个单词。第一个参数是要解析的字符串,第二个参数指定要提取的单词。
data firstlast;
input String $60.;
First_Name = scan(string, 1);
Last_Name = scan(string, -1);
datalines2;
John Smith;
Jane Cook;
;;;
run;
在 pandas 中提取单词的最简单方法是按空格分割字符串,然后按索引引用单词。请注意,如果您需要更强大的方法,还有其他方法。
更改大小写#
SAS 的 UPCASE 、 LOWCASE 和 PROPCASE 函数会更改参数的大小写。
data firstlast;
input String $60.;
string_up = UPCASE(string);
string_low = LOWCASE(string);
string_prop = PROPCASE(string);
datalines2;
John Smith;
Jane Cook;
;;;
run;
等效的 pandas 方法是 Series.str.upper() 、Series.str.lower() 和 Series.str.title() 。
合并#
以下表将用于合并示例:
在 SAS 中,数据必须在合并之前显式排序。不同类型的连接是通过使用 in= 虚拟变量来跟踪在一个或两个输入框架中是否找到匹配项来实现的。
proc sort data=df1;
by key;
run;
proc sort data=df2;
by key;
run;
data left_join inner_join right_join outer_join;
merge df1(in=a) df2(in=b);
if a and b then output inner_join;
if a then output left_join;
if b then output right_join;
if a or b then output outer_join;
run;
pandas DataFrames 有一个 merge() 方法,它提供了类似的功能。数据不必提前排序,并且可以通过 how 关键字来实现不同的连接类型。
缺失数据#
pandas 和 SAS 都有缺失数据的表示。
pandas 用特殊的浮点值 ``NaN``(非数字)表示缺失数据。其中许多语义是相同的;例如,缺失数据会在数值运算中传播,并且默认情况下会被聚合忽略。
其中一个不同之处在于,缺失数据无法与其哨兵值进行比较。例如,在 SAS 中,你可以执行此操作来过滤缺失值。
data outer_join_nulls;
set outer_join;
if value_x = .;
run;
data outer_join_no_nulls;
set outer_join;
if value_x ^= .;
run;
在 pandas 中,可以使用 Series.isna() 和 Series.notna() 来过滤行。
pandas 提供了 a variety of methods to work with missing data 。以下是一些示例:
删除包含缺失值的行#
向前填充(使用前一行值)#
用指定值替换缺失值#
使用平均值:
GroupBy#
聚合#
SAS 的 PROC SUMMARY 可用于按一个或多个键变量分组,并计算数值列上的聚合。
proc summary data=tips nway;
class sex smoker;
var total_bill tip;
output out=tips_summed sum=;
run;
pandas 提供了灵活的 groupby 机制,允许进行类似的聚合。有关更多详细信息和示例,请参阅 groupby documentation 。
转换#
在 SAS 中,如果需要将组聚合与原始数据框一起使用,则必须将其合并回。例如,按吸烟者组为每条观测值减去平均值。
proc summary data=tips missing nway;
class smoker;
var total_bill;
output out=smoker_means mean(total_bill)=group_bill;
run;
proc sort data=tips;
by smoker;
run;
data tips;
merge tips(in=a) smoker_means(in=b);
by smoker;
adj_total_bill = total_bill - group_bill;
if a and b;
run;
pandas 提供了 转换 机制,可以简洁地用一个操作表达此类运算。
按组处理#
除了聚合之外,pandas 的 groupby 还可以用于复制 SAS 中的大多数其他按组处理操作。例如,此 DATA 步按性别/吸烟者组读取数据,并过滤到每组的第一个条目。
proc sort data=tips;
by sex smoker;
run;
data tips_first;
set tips;
by sex smoker;
if FIRST.sex or FIRST.smoker then output;
run;
在 pandas 中,这可以写成:
其他注意事项#
磁盘与内存#
pandas 完全在内存中运行,而 SAS 数据集存在于磁盘上。这意味着能够在 pandas 中加载的数据量受限于您计算机的内存,但对这些数据的操作可能会更快。
如果需要非内存处理,一种可能性是 dask.dataframe 库(目前正在开发中),它为磁盘上的 DataFrame 提供了 pandas 功能的一个子集。
数据互操作#
pandas 提供了 read_sas() 方法,可以读取以 XPORT 或 SAS7BDAT 二进制格式保存的 SAS 数据。
libname xportout xport 'transport-file.xpt';
data xportout.tips;
set tips(rename=(total_bill=tbill));
* xport variable names limited to 6 characters;
run;
df = pd.read_sas("transport-file.xpt")
df = pd.read_sas("binary-file.sas7bdat")
您也可以直接指定文件格式。默认情况下,pandas 会尝试根据文件的扩展名推断文件格式。
df = pd.read_sas("transport-file.xpt", format="xport")
df = pd.read_sas("binary-file.sas7bdat", format="sas7bdat")
XPORT 是一种相对受限的格式,对其的解析不如 pandas 的其他读取器优化。在 SAS 和 pandas 之间进行数据互操作的另一种方法是序列化为 csv。
# version 0.17, 10M rows
In [8]: %time df = pd.read_sas('big.xpt')
Wall time: 14.6 s
In [9]: %time df = pd.read_csv('big.csv')
Wall time: 4.86 s