与 SAS 的比较#

对于可能来自 SAS 的用户,本页面旨在演示如何使用 pandas 执行不同的 SAS 操作。

如果你是 pandas 新手,你可能想先阅读 10 Minutes to pandas 来熟悉该库。

按照惯例,我们像这样导入 pandas 和 NumPy:

数据结构#

通用术语翻译#

pandas

SAS

DataFrame

data set

column

variable

row

observation

groupby

BY-group

NaN

.

DataFrame#

pandas 中的 DataFrame 类似于 SAS 数据集——一个二维数据源,具有标记的列,这些列可以是不同类型的。正如本文档所示,使用 SAS 的 DATA 语句几乎可以应用的任何操作,也可以在 pandas 中完成。

Series#

Series 是代表 DataFrame 单列的数据结构。SAS 没有单独的数据结构来表示单列,但总体而言,处理 Series 类似于在 DATA 步中引用列。

Index#

每个 DataFrameSeries 都有一个 Index —— 这是数据*行*的标签。SAS 没有完全类似的概。数据集的行本质上是无标签的,除了可以在 DATA 步中访问的隐式整数索引 (_N_)。

在 pandas 中,如果没有指定索引,默认也会使用整数索引(第一行 = 0,第二行 = 1,依此类推)。虽然使用带标签的 IndexMultiIndex 可以实现复杂的分析,并且最终是理解 pandas 的重要组成部分,但在此比较中,我们将基本忽略 Index,仅将 DataFrame 视为列的集合。有关如何有效使用 Index 的更多信息,请参阅 indexing documentation

副本 vs. 原地操作#

大多数 pandas 操作会返回 Series/DataFrame 的副本。要使更改“生效”,您需要将其赋给一个新变量:

sorted_df = df.sort_values("col1")

或者覆盖原始变量:

df = df.sort_values("col1")

备注

您会看到一些方法提供了 inplace=Truecopy=False 关键字参数:

df.replace(5, inplace=True)

目前正在积极讨论弃用和删除大多数方法的 inplacecopy``(例如,``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 通过指定单个 SeriesDataFrame 中提供矢量化操作。也可以用同样的方式分配新列。 DataFrame.drop() 方法会从 DataFrame 中删除一列。

过滤#

SAS 中的过滤是通过 ifwhere 语句对一个或多个列进行的。

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 中,可以使用来自 numpywhere 方法完成相同的操作。

日期功能#

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 使用 LENGTHNLENGTHC 函数确定字符的长度。LENGTHN 排除尾随空格,LENGTHC 包含尾随空格。

data _null_;
set tips;
put(LENGTHN(time));
put(LENGTHC(time));
run;

您可以使用 Series.str.len() 查找字符串的长度。在 Python 3 中,所有字符串都是 Unicode 字符串。len 包含尾随空格。使用 lenrstrip 来排除尾随空格。

查找子字符串位置#

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 的 UPCASELOWCASEPROPCASE 函数会更改参数的大小写。

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