Сравнение с SAS#

Для потенциальных пользователей, знакомых с SAS Эта страница предназначена для демонстрации того, как различные операции SAS выполняются в pandas.

Если вы новичок в pandas, возможно, вам сначала стоит прочитать 10 минут до pandas чтобы ознакомиться с библиотекой.

Как принято, мы импортируем pandas и NumPy следующим образом:

In [1]: import pandas as pd

In [2]: import numpy as np

Структуры данных#

Общий перевод терминологии#

pandas

SAS

DataFrame

набор данных

столбец

переменная

строка

наблюдение

groupby

BY-group

NaN

.

DataFrame#

A DataFrame в pandas аналогичен набору данных SAS - двумерному источнику данных с помеченными столбцами, которые могут быть разных типов. Как будет показано в этом документе, почти любую операцию, которую можно применить к набору данных с использованием DATA этап также может быть выполнен в pandas.

Series#

A Series это структура данных, представляющая один столбец DataFrame. SAS не имеет отдельной структуры данных для одного столбца, но в целом, работа с Series аналогично ссылке на столбец в DATA шаг.

Index#

Каждый DataFrame и Series имеет Index - которые являются метками на строки данных. SAS не имеет точно аналогичной концепции. Строки набора данных по сути не имеют меток, кроме неявного целочисленного индекса, который можно получить во время DATA шаг (_N_).

В pandas, если индекс не указан, по умолчанию также используется целочисленный индекс (первая строка = 0, вторая строка = 1 и так далее). При использовании помеченного Index или MultiIndex может обеспечить сложный анализ и в конечном итоге является важной частью pandas для понимания, но для этого сравнения мы в основном проигнорируем Index и просто рассматривать DataFrame как коллекцию столбцов. Пожалуйста, см. документация по индексированию для получения дополнительной информации о том, как использовать Index эффективно.

Копии 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. Предложение можно найти здесь.

Ввод / вывод данных#

Создание DataFrame из значений#

Набор данных SAS может быть создан из указанных значений путем размещения данных после datalines оператор и указание имен столбцов.

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

Pandas DataFrame может быть построен множеством различных способов, но для небольшого количества значений часто удобно задавать его как словарь Python, где ключи - это имена столбцов, а значения - данные.

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

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

Чтение внешних данных#

Как и SAS, pandas предоставляет утилиты для чтения данных из многих форматов. The tips набор данных, находящийся в тестах pandas (csv) будет использоваться во многих следующих примерах.

SAS предоставляет PROC IMPORT для чтения csv-данных в набор данных.

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

Метод pandas - это read_csv(), который работает аналогичным образом.

In [3]: url = (
   ...:     "https://raw.githubusercontent.com/pandas-dev/"
   ...:     "pandas/main/pandas/tests/io/data/csv/tips.csv"
   ...: )
   ...: 

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

In [5]: tips
Out[5]: 
     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
..          ...   ...     ...    ...   ...     ...   ...
239       29.03  5.92    Male     No   Sat  Dinner     3
240       27.18  2.00  Female    Yes   Sat  Dinner     2
241       22.67  2.00    Male    Yes   Sat  Dinner     2
242       17.82  1.75    Male     No   Sat  Dinner     2
243       18.78  3.00  Female     No  Thur  Dinner     2

[244 rows x 7 columns]

Как 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_* функции. См. Документация по вводу-выводу для получения дополнительной информации.

Ограничение вывода#

По умолчанию pandas будет обрезать вывод больших DataFrames для отображения первых и последних строк. Это можно переопределить с помощью изменение параметров pandas, или используя DataFrame.head() или DataFrame.tail().

In [1]: tips.head(5)
Out[1]: 
   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

Эквивалент в SAS будет:

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

Экспорт данных#

Обратная величина PROC IMPORT в SAS это 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.

In [1]: tips["total_bill"] = tips["total_bill"] - 2

In [2]: tips["new_bill"] = tips["total_bill"] / 2

In [3]: tips
Out[3]: 
     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
..          ...   ...     ...    ...   ...     ...   ...       ...
239       27.03  5.92    Male     No   Sat  Dinner     3    13.515
240       25.18  2.00  Female    Yes   Sat  Dinner     2    12.590
241       20.67  2.00    Male    Yes   Sat  Dinner     2    10.335
242       15.82  1.75    Male     No   Sat  Dinner     2     7.910
243       16.78  3.00  Female     No  Thur  Dinner     2     8.390

[244 rows x 8 columns]

In [4]: tips = tips.drop("new_bill", axis=1)

Фильтрация#

Фильтрация в 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;

DataFrames могут быть отфильтрованы несколькими способами; наиболее интуитивным из которых является использование булевую индексацию.

In [1]: tips[tips["total_bill"] > 10]
Out[1]: 
     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
..          ...   ...     ...    ...   ...     ...   ...
239       27.03  5.92    Male     No   Sat  Dinner     3
240       25.18  2.00  Female    Yes   Sat  Dinner     2
241       20.67  2.00    Male    Yes   Sat  Dinner     2
242       15.82  1.75    Male     No   Sat  Dinner     2
243       16.78  3.00  Female     No  Thur  Dinner     2

[204 rows x 7 columns]

Приведённое выражение просто передаёт Series of True/False объекты в DataFrame, возвращая все строки с True.

In [2]: is_dinner = tips["time"] == "Dinner"

In [3]: is_dinner
Out[3]: 
0      True
1      True
2      True
3      True
4      True
       ... 
239    True
240    True
241    True
242    True
243    True
Name: time, Length: 244, dtype: bool

In [4]: is_dinner.value_counts()
Out[4]: 
time
True     176
False     68
Name: count, dtype: int64

In [5]: tips[is_dinner]
Out[5]: 
     total_bill   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
..          ...   ...     ...    ...   ...     ...   ...
239       27.03  5.92    Male     No   Sat  Dinner     3
240       25.18  2.00  Female    Yes   Sat  Dinner     2
241       20.67  2.00    Male    Yes   Sat  Dinner     2
242       15.82  1.75    Male     No   Sat  Dinner     2
243       16.78  3.00  Female     No  Thur  Dinner     2

[176 rows x 7 columns]

Логика if/then#

В SAS логика if/then может использоваться для создания новых столбцов.

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

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

Та же операция в pandas может быть выполнена с использованием where метод из numpy.

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

In [2]: tips
Out[2]: 
     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
..          ...   ...     ...    ...   ...     ...   ...    ...
239       27.03  5.92    Male     No   Sat  Dinner     3   high
240       25.18  2.00  Female    Yes   Sat  Dinner     2   high
241       20.67  2.00    Male    Yes   Sat  Dinner     2   high
242       15.82  1.75    Male     No   Sat  Dinner     2   high
243       16.78  3.00  Female     No  Thur  Dinner     2   high

[244 rows x 8 columns]

Функциональность дат#

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 (такие как ресемплинг и пользовательские смещения) - см. документация по временным рядам для получения дополнительной информации.

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

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

In [3]: tips["date1_year"] = tips["date1"].dt.year

In [4]: tips["date2_month"] = tips["date2"].dt.month

In [5]: tips["date1_next"] = tips["date1"] + pd.offsets.MonthBegin()

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

In [7]: tips[
   ...:     ["date1", "date2", "date1_year", "date2_month", "date1_next", "months_between"]
   ...: ]
   ...: 
Out[7]: 
         date1      date2  date1_year  date2_month date1_next    months_between
0   2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>
1   2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>
2   2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>
3   2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>
4   2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>
..         ...        ...         ...          ...        ...               ...
239 2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>
240 2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>
241 2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>
242 2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>
243 2013-01-15 2015-02-15        2013            2 2013-02-01  <25 * MonthEnds>

[244 rows x 6 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 ниже.

Оставить определенные столбцы#

In [1]: tips[["sex", "total_bill", "tip"]]
Out[1]: 
        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
..      ...         ...   ...
239    Male       27.03  5.92
240  Female       25.18  2.00
241    Male       20.67  2.00
242    Male       15.82  1.75
243  Female       16.78  3.00

[244 rows x 3 columns]

Удалить столбец#

In [2]: tips.drop("sex", axis=1)
Out[2]: 
     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
..          ...   ...    ...   ...     ...   ...
239       27.03  5.92     No   Sat  Dinner     3
240       25.18  2.00    Yes   Sat  Dinner     2
241       20.67  2.00    Yes   Sat  Dinner     2
242       15.82  1.75     No   Sat  Dinner     2
243       16.78  3.00     No  Thur  Dinner     2

[244 rows x 6 columns]

Переименовать столбец#

In [3]: tips.rename(columns={"total_bill": "total_bill_2"})
Out[3]: 
     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
..            ...   ...     ...    ...   ...     ...   ...
239         27.03  5.92    Male     No   Sat  Dinner     3
240         25.18  2.00  Female    Yes   Sat  Dinner     2
241         20.67  2.00    Male    Yes   Sat  Dinner     2
242         15.82  1.75    Male     No   Sat  Dinner     2
243         16.78  3.00  Female     No  Thur  Dinner     2

[244 rows x 7 columns]

Сортировка по значениям#

Сортировка в SAS выполняется через PROC SORT

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

pandas имеет DataFrame.sort_values() метода, который принимает список столбцов для сортировки.

In [1]: tips = tips.sort_values(["sex", "total_bill"])

In [2]: tips
Out[2]: 
     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
..          ...    ...     ...    ...   ...     ...   ...
182       43.35   3.50    Male    Yes   Sun  Dinner     3
156       46.17   5.00    Male     No   Sun  Dinner     6
59        46.27   6.73    Male     No   Sat  Dinner     4
212       46.33   9.00    Male     No   Sat  Dinner     4
170       48.81  10.00    Male    Yes   Sat  Dinner     3

[244 rows x 7 columns]

Обработка строк#

Определение длины строки#

SAS определяет длину строки символов с помощью ДЛИНА и LENGTHC функций. LENGTHN исключает завершающие пробелы и LENGTHC включает завершающие пробелы.

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

Вы можете найти длину строки символов с помощью Series.str.len(). В Python 3 все строки являются строками Unicode. len включает завершающие пробелы. Используйте len и rstrip для исключения завершающих пробелов.

In [1]: tips["time"].str.len()
Out[1]: 
67     6
92     6
111    6
145    5
135    5
      ..
182    6
156    6
59     6
212    6
170    6
Name: time, Length: 244, dtype: int64

In [2]: tips["time"].str.rstrip().str.len()
Out[2]: 
67     6
92     6
111    6
145    5
135    5
      ..
182    6
156    6
59     6
212    6
170    6
Name: time, Length: 244, dtype: int64

Поиск позиции подстроки#

SAS определяет позицию символа в строке с помощью FINDW функция. FINDW принимает строку, определенную первым аргументом, и ищет первую позицию подстроки, которую вы указываете в качестве второго аргумента.

data _null_;
set tips;
put(FINDW(sex,'ale'));
run;

Вы можете найти позицию символа в столбце строк с помощью Series.str.find() метод. find ищет первую позицию подстроки. Если подстрока найдена, метод возвращает её позицию. Если не найдена, возвращает -1Имейте в виду, что индексы в Python начинаются с нуля.

In [1]: tips["sex"].str.find("ale")
Out[1]: 
67     3
92     3
111    3
145    3
135    3
      ..
182    1
156    1
59     1
212    1
170    1
Name: sex, Length: 244, dtype: int64

Извлечение подстроки по позиции#

SAS извлекает подстроку из строки на основе её позиции с помощью SUBSTR функция.

data _null_;
set tips;
put(substr(sex,1,1));
run;

С pandas вы можете использовать [] нотация для извлечения подстроки из строки по позициям. Помните, что в Python индексация начинается с нуля.

In [1]: tips["sex"].str[0:1]
Out[1]: 
67     F
92     F
111    F
145    F
135    F
      ..
182    M
156    M
59     M
212    M
170    M
Name: sex, Length: 244, dtype: object

Извлечение 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 — разделить строки по пробелам, затем обратиться к слову по индексу. Обратите внимание, что существуют более мощные подходы, если они вам понадобятся.

In [1]: firstlast = pd.DataFrame({"String": ["John Smith", "Jane Cook"]})

In [2]: firstlast["First_Name"] = firstlast["String"].str.split(" ", expand=True)[0]

In [3]: firstlast["Last_Name"] = firstlast["String"].str.rsplit(" ", expand=True)[1]

In [4]: firstlast
Out[4]: 
       String First_Name Last_Name
0  John Smith       John     Smith
1   Jane Cook       Jane      Cook

Изменение регистра#

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().

In [1]: firstlast = pd.DataFrame({"string": ["John Smith", "Jane Cook"]})

In [2]: firstlast["upper"] = firstlast["string"].str.upper()

In [3]: firstlast["lower"] = firstlast["string"].str.lower()

In [4]: firstlast["title"] = firstlast["string"].str.title()

In [5]: firstlast
Out[5]: 
       string       upper       lower       title
0  John Smith  JOHN SMITH  john smith  John Smith
1   Jane Cook   JANE COOK   jane cook   Jane Cook

Слияние#

Следующие таблицы будут использоваться в примерах слияния:

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

In [2]: df1
Out[2]: 
  key     value
0   A  0.469112
1   B -0.282863
2   C -1.509059
3   D -1.135632

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

In [4]: df2
Out[4]: 
  key     value
0   B  1.212112
1   D -0.173215
2   D  0.119209
3   E -1.044236

В 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 [1]: inner_join = df1.merge(df2, on=["key"], how="inner")

In [2]: inner_join
Out[2]: 
  key   value_x   value_y
0   B -0.282863  1.212112
1   D -1.135632 -0.173215
2   D -1.135632  0.119209

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

In [4]: left_join
Out[4]: 
  key   value_x   value_y
0   A  0.469112       NaN
1   B -0.282863  1.212112
2   C -1.509059       NaN
3   D -1.135632 -0.173215
4   D -1.135632  0.119209

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

In [6]: right_join
Out[6]: 
  key   value_x   value_y
0   B -0.282863  1.212112
1   D -1.135632 -0.173215
2   D -1.135632  0.119209
3   E       NaN -1.044236

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

In [8]: outer_join
Out[8]: 
  key   value_x   value_y
0   A  0.469112       NaN
1   B -0.282863  1.212112
2   C -1.509059       NaN
3   D -1.135632 -0.173215
4   D -1.135632  0.119209
5   E       NaN -1.044236

Отсутствующие данные#

Как pandas, так и SAS имеют представление для пропущенных данных.

pandas представляет отсутствующие данные специальным значением с плавающей точкой NaN (не число). Многие семантики совпадают; например, пропущенные данные распространяются через числовые операции и по умолчанию игнорируются при агрегациях.

In [1]: outer_join
Out[1]: 
  key   value_x   value_y
0   A  0.469112       NaN
1   B -0.282863  1.212112
2   C -1.509059       NaN
3   D -1.135632 -0.173215
4   D -1.135632  0.119209
5   E       NaN -1.044236

In [2]: outer_join["value_x"] + outer_join["value_y"]
Out[2]: 
0         NaN
1    0.929249
2         NaN
3   -1.308847
4   -1.016424
5         NaN
dtype: float64

In [3]: outer_join["value_x"].sum()
Out[3]: -3.5940742896293765

Одно отличие заключается в том, что пропущенные данные нельзя сравнивать с их сторожевой меткой. Например, в 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() можно использовать для фильтрации строк.

In [1]: outer_join[outer_join["value_x"].isna()]
Out[1]: 
  key  value_x   value_y
5   E      NaN -1.044236

In [2]: outer_join[outer_join["value_x"].notna()]
Out[2]: 
  key   value_x   value_y
0   A  0.469112       NaN
1   B -0.282863  1.212112
2   C -1.509059       NaN
3   D -1.135632 -0.173215
4   D -1.135632  0.119209

pandas предоставляет различные методы работы с пропущенными данными. Вот несколько примеров:

Удаление строк с пропущенными значениями#

In [3]: outer_join.dropna()
Out[3]: 
  key   value_x   value_y
1   B -0.282863  1.212112
3   D -1.135632 -0.173215
4   D -1.135632  0.119209

Заполнение вперед из предыдущих строк#

In [4]: outer_join.ffill()
Out[4]: 
  key   value_x   value_y
0   A  0.469112       NaN
1   B -0.282863  1.212112
2   C -1.509059  1.212112
3   D -1.135632 -0.173215
4   D -1.135632  0.119209
5   E -1.135632 -1.044236

Замена пропущенных значений указанным значением#

Использование среднего значения:

In [5]: outer_join["value_x"].fillna(outer_join["value_x"].mean())
Out[5]: 
0    0.469112
1   -0.282863
2   -1.509059
3   -1.135632
4   -1.135632
5   -0.718815
Name: value_x, dtype: float64

GroupBy#

Агрегация#

SAS’s PROC SUMMARY может использоваться для группировки по одной или нескольким ключевым переменным и вычисления агрегаций по числовым столбцам.

proc summary data=tips nway;
    class sex smoker;
    var total_bill tip;
    output out=tips_summed sum=;
run;

pandas предоставляет гибкий groupby механизм, позволяющий выполнять аналогичные агрегации. См. документация по groupby для получения дополнительной информации и примеров.

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

In [2]: tips_summed
Out[2]: 
               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

Преобразование#

В 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 предоставляет Преобразование механизм, позволяющий выражать такие операции кратко в одной операции.

In [1]: gb = tips.groupby("smoker")["total_bill"]

In [2]: tips["adj_total_bill"] = tips["total_bill"] - gb.transform("mean")

In [3]: tips
Out[3]: 
     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
..          ...    ...     ...    ...   ...     ...   ...             ...
182       43.35   3.50    Male    Yes   Sun  Dinner     3       24.593656
156       46.17   5.00    Male     No   Sun  Dinner     6       28.981722
59        46.27   6.73    Male     No   Sat  Dinner     4       29.081722
212       46.33   9.00    Male     No   Sat  Dinner     4       29.141722
170       48.81  10.00    Male    Yes   Sat  Dinner     3       30.053656

[244 rows x 8 columns]

Обработка по группам#

В дополнение к агрегации, 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 это будет записано как:

In [4]: tips.groupby(["sex", "smoker"]).first()
Out[4]: 
               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

Другие соображения#

Диск vs память#

pandas работает исключительно в памяти, тогда как набор данных SAS существует на диске. Это означает, что объем данных, который можно загрузить в pandas, ограничен памятью вашего компьютера, но также и то, что операции с этими данными могут выполняться быстрее.

Если требуется обработка данных вне оперативной памяти, одним из вариантов является dask.dataframe библиотека (в настоящее время в разработке), которая предоставляет подмножество функциональности pandas для работы с данными на диске DataFrame

Взаимодействие данных#

pandas предоставляет read_sas() метод, который может читать данные SAS, сохранённые в двоичном формате XPORT или SAS7BDAT.

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