Comparison with SAS

对于来自SAS的潜在用户,此页面旨在演示如何在pandas中执行不同的SAS操作。

如果你刚刚接触pandas,你可能需要先阅读10 Minutes to pandas,来熟悉这个库。

按照惯例,我们导入pandas和numpy如下:

In [1]: import pandas as pd

In [2]: import numpy as np

注意

在本教程中,将通过调用df.head()显示pandas DataFrame,它显示DataFrame这通常用于交互式工作(例如Jupyter notebook或terminal) - SAS中的等效项为:

proc print data=df(obs=5);
run;

Data Structures

General Terminology Translation

Pandas SAS
DataFrame 数据集
变量
观察
通过...分组 BY组
NaN .

DataFrame / Series

pandas中的DataFrame类似于SAS数据集 - 具有可以是不同类型的带标签列的二维数据源。如本文档所示,几乎任何可以应用于使用SAS的DATA步骤的数据集的操作也可以在pandas中完成。

Series是表示DataFrame的一列的数据结构。SAS没有单个列的单独数据结构,但通常,使用Series类似于引用DATA步骤中的列。

Index

每个DataFrameSeries都有一个Index - 它们是数据的上的标签。SAS没有完全类似的概念。除了在DATA步骤(_N_)期间可以访问的隐式整数索引,数据集的行基本上未标记。

在pandas中,如果未指定索引,则默认情况下也使用整数索引(第一行= 0,第二行= 1,依此类推)。While using a labeled Index or MultiIndex can enable sophisticated analyses and is ultimately an important part of pandas to understand, for this comparison we will essentially ignore the Index and just treat the DataFrame as a collection of columns. 有关如何有效使用Index的更多信息,请参阅indexing documentation

Data Input / Output

Constructing a DataFrame from Values

SAS数据集可以通过将数据放在datalines语句之后并指定列名来从指定的值构建。

data df;
    input x y;
    datalines;
    1 2
    3 4
    5 6
    ;
run;

可以以许多不同的方式构造pandas DataFrame,但对于少量的值,将其指定为python字典通常很方便,其中键是列名称,值是数据。

In [3]: df = pd.DataFrame({
   ...:           'x': [1, 3, 5],
   ...:           'y': [2, 4, 6]})
   ...: 

In [4]: df
Out[4]: 
   x  y
0  1  2
1  3  4
2  5  6

Reading External Data

像SAS一样,pandas提供了从多种格式读取数据的工具。在许多以下示例中将使用在pandas测试(csv)中找到的tips数据集。

SAS提供PROC IMPORT将csv数据读入数据集。

proc import datafile='tips.csv' dbms=csv out=tips replace;
    getnames=yes;
run;

pandas方法是read_csv(),其工作方式类似。

In [5]: url = 'https://raw.github.com/pandas-dev/pandas/master/pandas/tests/data/tips.csv'

In [6]: tips = pd.read_csv(url)

In [7]: tips.head()
Out[7]: 
   total_bill   tip     sex smoker  day    time  size
0       16.99  1.01  Female     No  Sun  Dinner     2
1       10.34  1.66    Male     No  Sun  Dinner     3
2       21.01  3.50    Male     No  Sun  Dinner     3
3       23.68  3.31    Male     No  Sun  Dinner     2
4       24.59  3.61  Female     No  Sun  Dinner     4

PROC IMPORTread_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)

除了text / csv,pandas还支持各种其他数据格式,如Excel,HDF5和SQL数据库。这些都通过pd.read_*函数读取。有关详细信息,请参阅IO documentation

Exporting Data

SAS中PROC IMPORT的逆是PROC EXPORT t3>

proc export data=tips outfile='tips2.csv' dbms=csv;
run;

类似地,在pandas中,与read_csv相反的是to_csv(),其他数据格式遵循类似的api。

tips.to_csv('tips2.csv')

Data Operations

Operations on Columns

DATA步骤中,可以对新列或现有列使用任意数学表达式。

data tips;
    set tips;
    total_bill = total_bill - 2;
    new_bill = total_bill / 2;
run;

pandas通过指定DataFrame中的个别Series来提供类似的向量化操作。新列可以以相同的方式分配。

In [8]: tips['total_bill'] = tips['total_bill'] - 2

In [9]: tips['new_bill'] = tips['total_bill'] / 2.0

In [10]: tips.head()
Out[10]: 
   total_bill   tip     sex smoker  day    time  size  new_bill
0       14.99  1.01  Female     No  Sun  Dinner     2     7.495
1        8.34  1.66    Male     No  Sun  Dinner     3     4.170
2       19.01  3.50    Male     No  Sun  Dinner     3     9.505
3       21.68  3.31    Male     No  Sun  Dinner     2    10.840
4       22.59  3.61  Female     No  Sun  Dinner     4    11.295

Filtering

在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;

DataFrames可以以多种方式进行过滤;其中最直观的是使用boolean indexing

In [11]: tips[tips['total_bill'] > 10].head()
Out[11]: 
   total_bill   tip     sex smoker  day    time  size
0       14.99  1.01  Female     No  Sun  Dinner     2
2       19.01  3.50    Male     No  Sun  Dinner     3
3       21.68  3.31    Male     No  Sun  Dinner     2
4       22.59  3.61  Female     No  Sun  Dinner     4
5       23.29  4.71    Male     No  Sun  Dinner     4

If/Then Logic

在SAS中,如果/然后逻辑可以用于创建新列。

data tips;
    set tips;
    format bucket $4.;

    if total_bill < 10 then bucket = 'low';
    else bucket = 'high';
run;

使用numpywhere方法可以实现在pandas中的相同操作。

In [12]: tips['bucket'] = np.where(tips['total_bill'] < 10, 'low', 'high')

In [13]: tips.head()
Out[13]: 
   total_bill   tip     sex smoker  day    time  size bucket
0       14.99  1.01  Female     No  Sun  Dinner     2   high
1        8.34  1.66    Male     No  Sun  Dinner     3    low
2       19.01  3.50    Male     No  Sun  Dinner     3   high
3       21.68  3.31    Male     No  Sun  Dinner     2   high
4       22.59  3.61  Female     No  Sun  Dinner     4   high

Date Functionality

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支持Base SAS中不提供的其他时间序列特性(例如重采样和自定义偏移) - 有关详细信息,请参阅timeseries documentation

In [14]: tips['date1'] = pd.Timestamp('2013-01-15')

In [15]: tips['date2'] = pd.Timestamp('2015-02-15')

In [16]: tips['date1_year'] = tips['date1'].dt.year

In [17]: tips['date2_month'] = tips['date2'].dt.month

In [18]: tips['date1_next'] = tips['date1'] + pd.offsets.MonthBegin()

In [19]: tips['months_between'] = (tips['date2'].dt.to_period('M') -
   ....:                           tips['date1'].dt.to_period('M'))
   ....: 

In [20]: tips[['date1','date2','date1_year','date2_month',
   ....:       'date1_next','months_between']].head()
   ....: 
Out[20]: 
       date1      date2  date1_year  date2_month date1_next months_between
0 2013-01-15 2015-02-15        2013            2 2013-02-01             25
1 2013-01-15 2015-02-15        2013            2 2013-02-01             25
2 2013-01-15 2015-02-15        2013            2 2013-02-01             25
3 2013-01-15 2015-02-15        2013            2 2013-02-01             25
4 2013-01-15 2015-02-15        2013            2 2013-02-01             25

Selection of Columns

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中表示。

# keep
In [21]: tips[['sex', 'total_bill', 'tip']].head()
Out[21]: 
      sex  total_bill   tip
0  Female       14.99  1.01
1    Male        8.34  1.66
2    Male       19.01  3.50
3    Male       21.68  3.31
4  Female       22.59  3.61

# drop
In [22]: tips.drop('sex', axis=1).head()
Out[22]: 
   total_bill   tip smoker  day    time  size
0       14.99  1.01     No  Sun  Dinner     2
1        8.34  1.66     No  Sun  Dinner     3
2       19.01  3.50     No  Sun  Dinner     3
3       21.68  3.31     No  Sun  Dinner     2
4       22.59  3.61     No  Sun  Dinner     4

# rename
In [23]: tips.rename(columns={'total_bill':'total_bill_2'}).head()
Out[23]: 
   total_bill_2   tip     sex smoker  day    time  size
0         14.99  1.01  Female     No  Sun  Dinner     2
1          8.34  1.66    Male     No  Sun  Dinner     3
2         19.01  3.50    Male     No  Sun  Dinner     3
3         21.68  3.31    Male     No  Sun  Dinner     2
4         22.59  3.61  Female     No  Sun  Dinner     4

Sorting by Values

SAS中的排序是通过PROC SORT

proc sort data=tips;
    by sex total_bill;
run;

pandas对象有一个sort_values()方法,它接受要排序的列的列表。

In [24]: tips = tips.sort_values(['sex', 'total_bill'])

In [25]: tips.head()
Out[25]: 
     total_bill   tip     sex smoker   day    time  size
67         1.07  1.00  Female    Yes   Sat  Dinner     1
92         3.75  1.00  Female    Yes   Fri  Dinner     2
111        5.25  1.00  Female     No   Sat  Dinner     1
145        6.35  1.50  Female     No  Thur   Lunch     2
135        6.51  1.25  Female     No  Thur   Lunch     2

Merging

下面的表将用于合并示例

In [26]: df1 = pd.DataFrame({'key': ['A', 'B', 'C', 'D'],
   ....:                     'value': np.random.randn(4)})
   ....: 

In [27]: df1
Out[27]: 
  key     value
0   A -0.857326
1   B  1.075416
2   C  0.371727
3   D  1.065735

In [28]: df2 = pd.DataFrame({'key': ['B', 'D', 'D', 'E'],
   ....:                      'value': np.random.randn(4)})
   ....: 

In [29]: df2
Out[29]: 
  key     value
0   B -0.227314
1   D  2.102726
2   D -0.092796
3   E  0.094694

在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关键字完成不同的连接类型。

In [30]: inner_join = df1.merge(df2, on=['key'], how='inner')

In [31]: inner_join
Out[31]: 
  key   value_x   value_y
0   B  1.075416 -0.227314
1   D  1.065735  2.102726
2   D  1.065735 -0.092796

In [32]: left_join = df1.merge(df2, on=['key'], how='left')

In [33]: left_join
Out[33]: 
  key   value_x   value_y
0   A -0.857326       NaN
1   B  1.075416 -0.227314
2   C  0.371727       NaN
3   D  1.065735  2.102726
4   D  1.065735 -0.092796

In [34]: right_join = df1.merge(df2, on=['key'], how='right')

In [35]: right_join
Out[35]: 
  key   value_x   value_y
0   B  1.075416 -0.227314
1   D  1.065735  2.102726
2   D  1.065735 -0.092796
3   E       NaN  0.094694

In [36]: outer_join = df1.merge(df2, on=['key'], how='outer')

In [37]: outer_join
Out[37]: 
  key   value_x   value_y
0   A -0.857326       NaN
1   B  1.075416 -0.227314
2   C  0.371727       NaN
3   D  1.065735  2.102726
4   D  1.065735 -0.092796
5   E       NaN  0.094694

Missing Data

像SAS一样,pandas有一个缺失数据的表示 - 这是特殊的浮动值NaN(不是数字)。许多语义是相同的,例如缺少的数据通过数值操作传播,并且对于聚合默认被忽略。

In [38]: outer_join
Out[38]: 
  key   value_x   value_y
0   A -0.857326       NaN
1   B  1.075416 -0.227314
2   C  0.371727       NaN
3   D  1.065735  2.102726
4   D  1.065735 -0.092796
5   E       NaN  0.094694

In [39]: outer_join['value_x'] + outer_join['value_y']
Out[39]: 
0         NaN
1    0.848102
2         NaN
3    3.168461
4    0.972939
5         NaN
dtype: float64

In [40]: outer_join['value_x'].sum()
Out[40]: 2.72128653544262

一个区别是,缺少的数据不能与其哨兵值进行比较。例如,在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中不起作用。而应该使用pd.isnullpd.notnull函数进行比较。

In [41]: outer_join[pd.isnull(outer_join['value_x'])]
Out[41]: 
  key  value_x   value_y
5   E      NaN  0.094694

In [42]: outer_join[pd.notnull(outer_join['value_x'])]
Out[42]: 
  key   value_x   value_y
0   A -0.857326       NaN
1   B  1.075416 -0.227314
2   C  0.371727       NaN
3   D  1.065735  2.102726
4   D  1.065735 -0.092796

pandas还提供了多种方法来处理缺失的数据 - 其中一些在SAS中表达是有挑战性的。例如,有一些方法删除具有任何缺失值的所有行,将缺失值替换为指定值(如平均值)或从前一行的向前填充。有关详情,请参阅missing data documentation

In [43]: outer_join.dropna()
Out[43]: 
  key   value_x   value_y
1   B  1.075416 -0.227314
3   D  1.065735  2.102726
4   D  1.065735 -0.092796

In [44]: outer_join.fillna(method='ffill')
Out[44]: 
  key   value_x   value_y
0   A -0.857326       NaN
1   B  1.075416 -0.227314
2   C  0.371727 -0.227314
3   D  1.065735  2.102726
4   D  1.065735 -0.092796
5   E  1.065735  0.094694

In [45]: outer_join['value_x'].fillna(outer_join['value_x'].mean())
Out[45]: 
0   -0.857326
1    1.075416
2    0.371727
3    1.065735
4    1.065735
5    0.544257
Name: value_x, dtype: float64

GroupBy

Aggregation

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

In [46]: tips_summed = tips.groupby(['sex', 'smoker'])['total_bill', 'tip'].sum()

In [47]: tips_summed.head()
Out[47]: 
               total_bill     tip
sex    smoker                    
Female No          869.68  149.77
       Yes         527.27   96.74
Male   No         1725.75  302.00
       Yes        1217.07  183.07

Transformation

在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 groubpy提供了一种transform机制,允许在一个操作中简洁地表达这些类型的操作。

In [48]: gb = tips.groupby('smoker')['total_bill']

In [49]: tips['adj_total_bill'] = tips['total_bill'] - gb.transform('mean')

In [50]: tips.head()
Out[50]: 
     total_bill   tip     sex smoker   day    time  size  adj_total_bill
67         1.07  1.00  Female    Yes   Sat  Dinner     1      -17.686344
92         3.75  1.00  Female    Yes   Fri  Dinner     2      -15.006344
111        5.25  1.00  Female     No   Sat  Dinner     1      -11.938278
145        6.35  1.50  Female     No  Thur   Lunch     2      -10.838278
135        6.51  1.25  Female     No  Thur   Lunch     2      -10.678278

By Group Processing

除了聚合之外,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;

在大熊猫这将写成:

In [51]: tips.groupby(['sex','smoker']).first()
Out[51]: 
               total_bill   tip   day    time  size  adj_total_bill
sex    smoker                                                      
Female No            5.25  1.00   Sat  Dinner     1      -11.938278
       Yes           1.07  1.00   Sat  Dinner     1      -17.686344
Male   No            5.51  2.00  Thur   Lunch     2      -11.678278
       Yes           5.25  5.15   Sun  Dinner     2      -13.506344

Other Considerations

Disk vs Memory

pandas仅在内存中运行,其中SAS数据集存在于磁盘上。这意味着能够在pandas中加载的数据的大小受到机器内存的限制,而且对该数据的操作可能更快。

如果需要核心处理,一种可能性是dask.dataframe库(当前正在开发中),它为磁盘上的DataFrame提供了一个pandas功能的子集,

Data Interop

pandas提供了一个可以读取以XPORT格式保存的SAS数据的read_sas()方法。计划在将来的版本中读取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')

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
Scroll To Top