Pandas 與其他工具比較

2022-07-01 14:54 更新

與R/R庫的比較

由于 pandas 旨在為人們提供可以替代R的大量數(shù)據(jù)操作和分析的功能,因此本章節(jié)會提供較為詳細的R語言的介紹以及與相關(guān)的許多第三方庫的對比說明,比如我們的 pandas 庫。在與R和CRAN庫的比較中,我們關(guān)注以下事項:

  • 功能/靈活性:每個工具可以/不??可以做什么
  • 性能:操作速度有多快。硬性數(shù)字/基準是優(yōu)選的
  • 易于使用:一種工具更容易/更難使用(您可能需要對此進行判斷,并進行并排代碼比較)

此頁面還為這些R包的用戶提供了一些翻譯指南。

要將 DataFrame 對象從 pandas 轉(zhuǎn)化為到 R 的數(shù)據(jù)類型,有一個選擇是采用HDF5文件,請參閱外部兼容性示例。

#快速參考

我們將從快速參考指南開始,將dplyr與pandas等效的一些常見R操作配對。

#查詢、過濾、采樣

RPandas
dim(df)df.shape
head(df)df.head()
slice(df, 1:10)df.iloc[:9]
filter(df, col1 == 1, col2 == 1)df.query('col1 == 1 & col2 == 1')
df[df$col1 == 1 & df$col2 == 1,]df[(df.col1 == 1) & (df.col2 == 1)]
select(df, col1, col2)df[['col1', 'col2']]
select(df, col1:col3)df.loc[:, 'col1':'col3']
select(df, -(col1:col3))df.drop(cols_to_drop, axis=1)但是看[1]
distinct(select(df, col1))df[['col1']].drop_duplicates()
distinct(select(df, col1, col2))df[['col1', 'col2']].drop_duplicates()
sample_n(df, 10)df.sample(n=10)
sample_frac(df, 0.01)df.sample(frac=0.01)

Note

R表示列的子集 (select(df,col1:col3) 的縮寫更接近 Pandas 的寫法,如果您有列的列表,例如 df[cols[1:3] 或 df.drop(cols[1:3]),按列名執(zhí)行此操作可能會引起混亂。

#排序

RPandas
arrange(df, col1, col2)df.sort_values(['col1', 'col2'])
arrange(df, desc(col1))df.sort_values('col1', ascending=False)

#變換

RPandas
select(df, col_one = col1)df.rename(columns={'col1': 'col_one'})['col_one']
rename(df, col_one = col1)df.rename(columns={'col1': 'col_one'})
mutate(df, c=a-b)df.assign(c=df.a-df.b)

#分組和組合

RPandas
summary(df)df.describe()
gdf <- group_by(df, col1)gdf = df.groupby('col1')
summarise(gdf, avg=mean(col1, na.rm=TRUE))df.groupby('col1').agg({'col1': 'mean'})
summarise(gdf, total=sum(col1))df.groupby('col1').sum()

#基本的R用法

#用Rc方法來進行切片操作

R使您可以輕松地按名稱訪問列(data.frame)

df <- data.frame(a=rnorm(5), b=rnorm(5), c=rnorm(5), d=rnorm(5), e=rnorm(5))
df[, c("a", "c", "e")]

或整數(shù)位置

df <- data.frame(matrix(rnorm(1000), ncol=100))
df[, c(1:10, 25:30, 40, 50:100)]

按名稱選擇多個pandas的列非常簡單

In [1]: df = pd.DataFrame(np.random.randn(10, 3), columns=list('abc'))

In [2]: df[['a', 'c']]
Out[2]: 
          a         c
0  0.469112 -1.509059
1 -1.135632 -0.173215
2  0.119209 -0.861849
3 -2.104569  1.071804
4  0.721555 -1.039575
5  0.271860  0.567020
6  0.276232 -0.673690
7  0.113648  0.524988
8  0.404705 -1.715002
9 -1.039268 -1.157892

In [3]: df.loc[:, ['a', 'c']]
Out[3]: 
          a         c
0  0.469112 -1.509059
1 -1.135632 -0.173215
2  0.119209 -0.861849
3 -2.104569  1.071804
4  0.721555 -1.039575
5  0.271860  0.567020
6  0.276232 -0.673690
7  0.113648  0.524988
8  0.404705 -1.715002
9 -1.039268 -1.157892

通過整數(shù)位置選擇多個不連續(xù)的列可以通過iloc索引器屬性和 numpy.r_ 的組合來實現(xiàn)。

In [4]: named = list('abcdefg')

In [5]: n = 30

In [6]: columns = named + np.arange(len(named), n).tolist()

In [7]: df = pd.DataFrame(np.random.randn(n, n), columns=columns)

In [8]: df.iloc[:, np.r_[:10, 24:30]]
Out[8]: 
           a         b         c         d         e         f         g         7         8         9        24        25        26        27        28        29
0  -1.344312  0.844885  1.075770 -0.109050  1.643563 -1.469388  0.357021 -0.674600 -1.776904 -0.968914 -1.170299 -0.226169  0.410835  0.813850  0.132003 -0.827317
1  -0.076467 -1.187678  1.130127 -1.436737 -1.413681  1.607920  1.024180  0.569605  0.875906 -2.211372  0.959726 -1.110336 -0.619976  0.149748 -0.732339  0.687738
2   0.176444  0.403310 -0.154951  0.301624 -2.179861 -1.369849 -0.954208  1.462696 -1.743161 -0.826591  0.084844  0.432390  1.519970 -0.493662  0.600178  0.274230
3   0.132885 -0.023688  2.410179  1.450520  0.206053 -0.251905 -2.213588  1.063327  1.266143  0.299368 -2.484478 -0.281461  0.030711  0.109121  1.126203 -0.977349
4   1.474071 -0.064034 -1.282782  0.781836 -1.071357  0.441153  2.353925  0.583787  0.221471 -0.744471 -1.197071 -1.066969 -0.303421 -0.858447  0.306996 -0.028665
..       ...       ...       ...       ...       ...       ...       ...       ...       ...       ...       ...       ...       ...       ...       ...       ...
25  1.492125 -0.068190  0.681456  1.221829 -0.434352  1.204815 -0.195612  1.251683 -1.040389 -0.796211  1.944517  0.042344 -0.307904  0.428572  0.880609  0.487645
26  0.725238  0.624607 -0.141185 -0.143948 -0.328162  2.095086 -0.608888 -0.926422  1.872601 -2.513465 -0.846188  1.190624  0.778507  1.008500  1.424017  0.717110
27  1.262419  1.950057  0.301038 -0.933858  0.814946  0.181439 -0.110015 -2.364638 -1.584814  0.307941 -1.341814  0.334281 -0.162227  1.007824  2.826008  1.458383
28 -1.585746 -0.899734  0.921494 -0.211762 -0.059182  0.058308  0.915377 -0.696321  0.150664 -3.060395  0.403620 -0.026602 -0.240481  0.577223 -1.088417  0.326687
29 -0.986248  0.169729 -1.158091  1.019673  0.646039  0.917399 -0.010435  0.366366  0.922729  0.869610 -1.209247 -0.671466  0.332872 -2.013086 -1.602549  0.333109

[30 rows x 16 columns]

#aggregate

在R中,您可能希望將數(shù)據(jù)分成幾個子集,并計算每個子集的平均值。使用名為df的data.frame并將其分成組by1和by2:

df <- data.frame(
  v1 = c(1,3,5,7,8,3,5,NA,4,5,7,9),
  v2 = c(11,33,55,77,88,33,55,NA,44,55,77,99),
  by1 = c("red", "blue", 1, 2, NA, "big", 1, 2, "red", 1, NA, 12),
  by2 = c("wet", "dry", 99, 95, NA, "damp", 95, 99, "red", 99, NA, NA))
aggregate(x=df[, c("v1", "v2")], by=list(mydf2$by1, mydf2$by2), FUN = mean)

該groupby()方法類似于基本R的 aggregate 函數(shù)。

In [9]: df = pd.DataFrame(
   ...:     {'v1': [1, 3, 5, 7, 8, 3, 5, np.nan, 4, 5, 7, 9],
   ...:      'v2': [11, 33, 55, 77, 88, 33, 55, np.nan, 44, 55, 77, 99],
   ...:      'by1': ["red", "blue", 1, 2, np.nan, "big", 1, 2, "red", 1, np.nan, 12],
   ...:      'by2': ["wet", "dry", 99, 95, np.nan, "damp", 95, 99, "red", 99, np.nan,
   ...:              np.nan]})
   ...: 

In [10]: g = df.groupby(['by1', 'by2'])

In [11]: g[['v1', 'v2']].mean()
Out[11]: 
            v1    v2
by1  by2            
1    95    5.0  55.0
     99    5.0  55.0
2    95    7.0  77.0
     99    NaN   NaN
big  damp  3.0  33.0
blue dry   3.0  33.0
red  red   4.0  44.0
     wet   1.0  11.0

有關(guān)更多詳細信息和示例,請參閱groupby文檔。

#match/ %in%

在R中選擇數(shù)據(jù)的常用方法是使用%in%使用該函數(shù)定義的數(shù)據(jù)match。運算符%in%用于返回指示是否存在匹配的邏輯向量:

s <- 0:4
s %in% c(2,4)

該isin()方法類似于R %in%運算符:

In [12]: s = pd.Series(np.arange(5), dtype=np.float32)

In [13]: s.isin([2, 4])
Out[13]: 
0    False
1    False
2     True
3    False
4     True
dtype: bool

該match函數(shù)返回其第二個參數(shù)匹配位置的向量:

s <- 0:4
match(s, c(2,4))

有關(guān)更多詳細信息和示例,請參閱重塑文檔。

#tapply

tapply類似于aggregate,但數(shù)據(jù)可以是一個參差不齊的數(shù)組,因為子類大小可能是不規(guī)則的。使用調(diào)用的data.frame baseball,并根據(jù)數(shù)組檢索信息team:

baseball <-
  data.frame(team = gl(5, 5,
             labels = paste("Team", LETTERS[1:5])),
             player = sample(letters, 25),
             batting.average = runif(25, .200, .400))

tapply(baseball$batting.average, baseball.example$team,
       max)

在pandas我們可以使用pivot_table()方法來處理這個:

In [14]: import random

In [15]: import string

In [16]: baseball = pd.DataFrame(
   ....:     {'team': ["team %d" % (x + 1) for x in range(5)] * 5,
   ....:      'player': random.sample(list(string.ascii_lowercase), 25),
   ....:      'batting avg': np.random.uniform(.200, .400, 25)})
   ....: 

In [17]: baseball.pivot_table(values='batting avg', columns='team', aggfunc=np.max)
Out[17]: 
team           team 1    team 2    team 3    team 4    team 5
batting avg  0.352134  0.295327  0.397191  0.394457  0.396194

有關(guān)更多詳細信息和示例,請參閱重塑文檔。

#subset

該query()方法類似于基本R subset 函數(shù)。在R中,您可能希望獲取data.frame一列的值小于另一列的值的行:

df <- data.frame(a=rnorm(10), b=rnorm(10))
subset(df, a <= b)
df[df$a <= df$b,]  # note the comma

在pandas,有幾種方法可以執(zhí)行子集化。您可以使用 query()或傳遞表達式,就像它是索引/切片以及標準布爾索引一樣:

In [18]: df = pd.DataFrame({'a': np.random.randn(10), 'b': np.random.randn(10)})

In [19]: df.query('a <= b')
Out[19]: 
          a         b
1  0.174950  0.552887
2 -0.023167  0.148084
3 -0.495291 -0.300218
4 -0.860736  0.197378
5 -1.134146  1.720780
7 -0.290098  0.083515
8  0.238636  0.946550

In [20]: df[df.a <= df.b]
Out[20]: 
          a         b
1  0.174950  0.552887
2 -0.023167  0.148084
3 -0.495291 -0.300218
4 -0.860736  0.197378
5 -1.134146  1.720780
7 -0.290098  0.083515
8  0.238636  0.946550

In [21]: df.loc[df.a <= df.b]
Out[21]: 
          a         b
1  0.174950  0.552887
2 -0.023167  0.148084
3 -0.495291 -0.300218
4 -0.860736  0.197378
5 -1.134146  1.720780
7 -0.290098  0.083515
8  0.238636  0.946550

有關(guān)更多詳細信息和示例,請參閱查詢文檔。

#with

使用df帶有列的R中調(diào)用的data.frame的表達式a, b將使用with如下方式進行求值:

df <- data.frame(a=rnorm(10), b=rnorm(10))
with(df, a + b)
df$a + df$b  # same as the previous expression

在pandas等效表達式中,使用該 eval()方法將是:

In [22]: df = pd.DataFrame({'a': np.random.randn(10), 'b': np.random.randn(10)})

In [23]: df.eval('a + b')
Out[23]: 
0   -0.091430
1   -2.483890
2   -0.252728
3   -0.626444
4   -0.261740
5    2.149503
6   -0.332214
7    0.799331
8   -2.377245
9    2.104677
dtype: float64

In [24]: df.a + df.b  # same as the previous expression
Out[24]: 
0   -0.091430
1   -2.483890
2   -0.252728
3   -0.626444
4   -0.261740
5    2.149503
6   -0.332214
7    0.799331
8   -2.377245
9    2.104677
dtype: float64

在某些情況下,eval()將比純Python中的評估快得多。有關(guān)更多詳細信息和示例,請參閱eval文檔。

#plyr

plyr是用于數(shù)據(jù)分析的拆分應(yīng)用組合策略的R庫。這些函數(shù)圍繞R,a for arrays,lfor lists和dfor中的三個數(shù)據(jù)結(jié)構(gòu)data.frame。下表顯示了如何在Python中映射這些數(shù)據(jù)結(jié)構(gòu)。

RPython
arraylist
lists字典(dist)或?qū)ο罅斜恚╨ist of objects)
data.framedataframe

#ddply

在R中使用名為df的data.frame的表達式,比如您有一個希望按月匯總x的需求:

require(plyr)
df <- data.frame(
  x = runif(120, 1, 168),
  y = runif(120, 7, 334),
  z = runif(120, 1.7, 20.7),
  month = rep(c(5,6,7,8),30),
  week = sample(1:4, 120, TRUE)
)

ddply(df, .(month, week), summarize,
      mean = round(mean(x), 2),
      sd = round(sd(x), 2))

在pandas等效表達式中,使用該 groupby()方法將是:

In [25]: df = pd.DataFrame({'x': np.random.uniform(1., 168., 120),
   ....:                    'y': np.random.uniform(7., 334., 120),
   ....:                    'z': np.random.uniform(1.7, 20.7, 120),
   ....:                    'month': [5, 6, 7, 8] * 30,
   ....:                    'week': np.random.randint(1, 4, 120)})
   ....: 

In [26]: grouped = df.groupby(['month', 'week'])

In [27]: grouped['x'].agg([np.mean, np.std])
Out[27]: 
                  mean        std
month week                       
5     1      63.653367  40.601965
      2      78.126605  53.342400
      3      92.091886  57.630110
6     1      81.747070  54.339218
      2      70.971205  54.687287
      3     100.968344  54.010081
7     1      61.576332  38.844274
      2      61.733510  48.209013
      3      71.688795  37.595638
8     1      62.741922  34.618153
      2      91.774627  49.790202
      3      73.936856  60.773900

有關(guān)更多詳細信息和示例,請參閱groupby文檔。

#重塑/ reshape2

#melt.array

使用a在R中調(diào)用的3維數(shù)組的表達式,您希望將其融合到data.frame中:

a <- array(c(1:23, NA), c(2,3,4))
data.frame(melt(a))

在Python中,既然a是一個列表,你可以簡單地使用列表理解。

In [28]: a = np.array(list(range(1, 24)) + [np.NAN]).reshape(2, 3, 4)

In [29]: pd.DataFrame([tuple(list(x) + [val]) for x, val in np.ndenumerate(a)])
Out[29]: 
    0  1  2     3
0   0  0  0   1.0
1   0  0  1   2.0
2   0  0  2   3.0
3   0  0  3   4.0
4   0  1  0   5.0
.. .. .. ..   ...
19  1  1  3  20.0
20  1  2  0  21.0
21  1  2  1  22.0
22  1  2  2  23.0
23  1  2  3   NaN

[24 rows x 4 columns]

#melt.list

使用aR中調(diào)用的列表的表達式,您希望將其融合到data.frame中:

a <- as.list(c(1:4, NA))
data.frame(melt(a))

在Python中,此列表將是元組列表,因此 DataFrame()方法會根據(jù)需要將其轉(zhuǎn)換為數(shù)據(jù)幀。

In [30]: a = list(enumerate(list(range(1, 5)) + [np.NAN]))

In [31]: pd.DataFrame(a)
Out[31]: 
   0    1
0  0  1.0
1  1  2.0
2  2  3.0
3  3  4.0
4  4  NaN

有關(guān)更多詳細信息和示例,請參閱“進入數(shù)據(jù)結(jié)構(gòu)”文檔。

#melt.data.frame

使用cheese在R中調(diào)用的data.frame的表達式,您要在其中重新整形data.frame:

cheese <- data.frame(
  first = c('John', 'Mary'),
  last = c('Doe', 'Bo'),
  height = c(5.5, 6.0),
  weight = c(130, 150)
)
melt(cheese, id=c("first", "last"))

在Python中,該melt()方法是R等價物:

In [32]: cheese = pd.DataFrame({'first': ['John', 'Mary'],
   ....:                        'last': ['Doe', 'Bo'],
   ....:                        'height': [5.5, 6.0],
   ....:                        'weight': [130, 150]})
   ....: 

In [33]: pd.melt(cheese, id_vars=['first', 'last'])
Out[33]: 
  first last variable  value
0  John  Doe   height    5.5
1  Mary   Bo   height    6.0
2  John  Doe   weight  130.0
3  Mary   Bo   weight  150.0

In [34]: cheese.set_index(['first', 'last']).stack()  # alternative way
Out[34]: 
first  last        
John   Doe   height      5.5
             weight    130.0
Mary   Bo    height      6.0
             weight    150.0
dtype: float64

有關(guān)更多詳細信息和示例,請參閱重塑文檔。

#cast

在R中acast是一個表達式,它使用df在R中調(diào)用的data.frame 來轉(zhuǎn)換為更高維的數(shù)組:

df <- data.frame(
  x = runif(12, 1, 168),
  y = runif(12, 7, 334),
  z = runif(12, 1.7, 20.7),
  month = rep(c(5,6,7),4),
  week = rep(c(1,2), 6)
)

mdf <- melt(df, id=c("month", "week"))
acast(mdf, week ~ month ~ variable, mean)

在Python中,最好的方法是使用pivot_table():

In [35]: df = pd.DataFrame({'x': np.random.uniform(1., 168., 12),
   ....:                    'y': np.random.uniform(7., 334., 12),
   ....:                    'z': np.random.uniform(1.7, 20.7, 12),
   ....:                    'month': [5, 6, 7] * 4,
   ....:                    'week': [1, 2] * 6})
   ....: 

In [36]: mdf = pd.melt(df, id_vars=['month', 'week'])

In [37]: pd.pivot_table(mdf, values='value', index=['variable', 'week'],
   ....:                columns=['month'], aggfunc=np.mean)
   ....: 
Out[37]: 
month                  5           6           7
variable week                                   
x        1     93.888747   98.762034   55.219673
         2     94.391427   38.112932   83.942781
y        1     94.306912  279.454811  227.840449
         2     87.392662  193.028166  173.899260
z        1     11.016009   10.079307   16.170549
         2      8.476111   17.638509   19.003494

類似地dcast,使用dfR中調(diào)用的data.frame 來基于Animal和聚合信息FeedType:

df <- data.frame(
  Animal = c('Animal1', 'Animal2', 'Animal3', 'Animal2', 'Animal1',
             'Animal2', 'Animal3'),
  FeedType = c('A', 'B', 'A', 'A', 'B', 'B', 'A'),
  Amount = c(10, 7, 4, 2, 5, 6, 2)
)

dcast(df, Animal ~ FeedType, sum, fill=NaN)
# Alternative method using base R
with(df, tapply(Amount, list(Animal, FeedType), sum))

Python可以通過兩種不同的方式處理它。首先,類似于上面使用pivot_table():

In [38]: df = pd.DataFrame({
   ....:     'Animal': ['Animal1', 'Animal2', 'Animal3', 'Animal2', 'Animal1',
   ....:                'Animal2', 'Animal3'],
   ....:     'FeedType': ['A', 'B', 'A', 'A', 'B', 'B', 'A'],
   ....:     'Amount': [10, 7, 4, 2, 5, 6, 2],
   ....: })
   ....: 

In [39]: df.pivot_table(values='Amount', index='Animal', columns='FeedType',
   ....:                aggfunc='sum')
   ....: 
Out[39]: 
FeedType     A     B
Animal              
Animal1   10.0   5.0
Animal2    2.0  13.0
Animal3    6.0   NaN

第二種方法是使用該groupby()方法:

In [40]: df.groupby(['Animal', 'FeedType'])['Amount'].sum()
Out[40]: 
Animal   FeedType
Animal1  A           10
         B            5
Animal2  A            2
         B           13
Animal3  A            6
Name: Amount, dtype: int64

有關(guān)更多詳細信息和示例,請參閱重新整形文檔或groupby文檔。

#factor

pandas具有分類數(shù)據(jù)的數(shù)據(jù)類型。

cut(c(1,2,3,4,5,6), 3)
factor(c(1,2,3,2,2,3))

在Pandas,這是完成與pd.cut和astype("category"):

In [41]: pd.cut(pd.Series([1, 2, 3, 4, 5, 6]), 3)
Out[41]: 
0    (0.995, 2.667]
1    (0.995, 2.667]
2    (2.667, 4.333]
3    (2.667, 4.333]
4      (4.333, 6.0]
5      (4.333, 6.0]
dtype: category
Categories (3, interval[float64]): [(0.995, 2.667] < (2.667, 4.333] < (4.333, 6.0]]

In [42]: pd.Series([1, 2, 3, 2, 2, 3]).astype("category")
Out[42]: 
0    1
1    2
2    3
3    2
4    2
5    3
dtype: category
Categories (3, int64): [1, 2, 3]

有關(guān)更多詳細信息和示例,請參閱分類介紹和 API文檔。還有一個關(guān)于R因子差異的文檔 。

#與SQL比較

由于許多潛在的 pandas 用戶對SQL有一定的了解 ,因此本頁面旨在提供一些使用pandas如何執(zhí)行各種SQL操作的示例。

如果您是 pandas 的新手,您可能需要先閱讀十分鐘入門Pandas 以熟悉本庫。

按照慣例,我們按如下方式導(dǎo)入 pandas 和 NumPy:

In [1]: import pandas as pd

In [2]: import numpy as np

大多數(shù)示例將使用tipspandas測試中找到的數(shù)據(jù)集。我們將數(shù)據(jù)讀入名為tips的DataFrame中,并假設(shè)我們有一個具有相同名稱和結(jié)構(gòu)的數(shù)據(jù)庫表。

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

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

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

#SELECT

在SQL中,使用您要選擇的以逗號分隔的列列表(或* 選擇所有列)來完成選擇:

SELECT total_bill, tip, smoker, time
FROM tips
LIMIT 5;

使用pandas,通過將列名列表傳遞給DataFrame來完成列選擇:

In [6]: tips[['total_bill', 'tip', 'smoker', 'time']].head(5)
Out[6]: 
   total_bill   tip smoker    time
0       16.99  1.01     No  Dinner
1       10.34  1.66     No  Dinner
2       21.01  3.50     No  Dinner
3       23.68  3.31     No  Dinner
4       24.59  3.61     No  Dinner

在沒有列名列表的情況下調(diào)用DataFrame將顯示所有列(類似于SQL*)。

#WHERE

SQL中的過濾是通過WHERE子句完成的。

SELECT *
FROM tips
WHERE time = 'Dinner'
LIMIT 5;

DataFrame可以通過多種方式進行過濾; 最直觀的是使用 布爾索引。

In [7]: tips[tips['time'] == 'Dinner'].head(5)
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

上面的語句只是將一個 Series 的 True / False 對象傳遞給 DataFrame,返回所有帶有True的行。

In [8]: is_dinner = tips['time'] == 'Dinner'

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

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

就像SQL的OR和AND一樣,可以使用|將多個條件傳遞給DataFrame (OR)和&(AND)。

-- tips of more than $5.00 at Dinner meals
SELECT *
FROM tips
WHERE time = 'Dinner' AND tip > 5.00;
# tips of more than $5.00 at Dinner meals
In [11]: tips[(tips['time'] == 'Dinner') & (tips['tip'] > 5.00)]
Out[11]: 
     total_bill    tip     sex smoker  day    time  size
23        39.42   7.58    Male     No  Sat  Dinner     4
44        30.40   5.60    Male     No  Sun  Dinner     4
47        32.40   6.00    Male     No  Sun  Dinner     4
52        34.81   5.20  Female     No  Sun  Dinner     4
59        48.27   6.73    Male     No  Sat  Dinner     4
116       29.93   5.07    Male     No  Sun  Dinner     4
155       29.85   5.14  Female     No  Sun  Dinner     5
170       50.81  10.00    Male    Yes  Sat  Dinner     3
172        7.25   5.15    Male    Yes  Sun  Dinner     2
181       23.33   5.65    Male    Yes  Sun  Dinner     2
183       23.17   6.50    Male    Yes  Sun  Dinner     4
211       25.89   5.16    Male    Yes  Sat  Dinner     4
212       48.33   9.00    Male     No  Sat  Dinner     4
214       28.17   6.50  Female    Yes  Sat  Dinner     3
239       29.03   5.92    Male     No  Sat  Dinner     3
-- tips by parties of at least 5 diners OR bill total was more than $45
SELECT *
FROM tips
WHERE size >= 5 OR total_bill > 45;
# tips by parties of at least 5 diners OR bill total was more than $45
In [12]: tips[(tips['size'] >= 5) | (tips['total_bill'] > 45)]
Out[12]: 
     total_bill    tip     sex smoker   day    time  size
59        48.27   6.73    Male     No   Sat  Dinner     4
125       29.80   4.20  Female     No  Thur   Lunch     6
141       34.30   6.70    Male     No  Thur   Lunch     6
142       41.19   5.00    Male     No  Thur   Lunch     5
143       27.05   5.00  Female     No  Thur   Lunch     6
155       29.85   5.14  Female     No   Sun  Dinner     5
156       48.17   5.00    Male     No   Sun  Dinner     6
170       50.81  10.00    Male    Yes   Sat  Dinner     3
182       45.35   3.50    Male    Yes   Sun  Dinner     3
185       20.69   5.00    Male     No   Sun  Dinner     5
187       30.46   2.00    Male    Yes   Sun  Dinner     5
212       48.33   9.00    Male     No   Sat  Dinner     4
216       28.15   3.00    Male    Yes   Sat  Dinner     5

使用notna()和isna() 方法完成NULL檢查。

In [13]: frame = pd.DataFrame({'col1': ['A', 'B', np.NaN, 'C', 'D'],
   ....:                       'col2': ['F', np.NaN, 'G', 'H', 'I']})
   ....: 

In [14]: frame
Out[14]: 
  col1 col2
0    A    F
1    B  NaN
2  NaN    G
3    C    H
4    D    I

假設(shè)我們有一個與上面的DataFrame結(jié)構(gòu)相同的表。我們只能col2通過以下查詢看到IS NULL 的記錄:

SELECT *
FROM frame
WHERE col2 IS NULL;
In [15]: frame[frame['col2'].isna()]
Out[15]: 
  col1 col2
1    B  NaN

獲取col1IS NOT NULL的項目可以完成notna()。

SELECT *
FROM frame
WHERE col1 IS NOT NULL;
In [16]: frame[frame['col1'].notna()]
Out[16]: 
  col1 col2
0    A    F
1    B  NaN
3    C    H
4    D    I

#GROUP BY

在pandas中,SQL的GROUP BY操作使用類似命名的 groupby()方法執(zhí)行。groupby()通常是指我們想要將數(shù)據(jù)集拆分成組,應(yīng)用某些功能(通常是聚合),然后將這些組合在一起的過程。

常見的SQL操作是獲取整個數(shù)據(jù)集中每個組中的記錄數(shù)。例如,有一個需要向我們提供提示中的性別的數(shù)量的查詢語句:

SELECT sex, count(*)
FROM tips
GROUP BY sex;
/*
Female     87
Male      157
*/

在 pandas 中可以這樣:

In [17]: tips.groupby('sex').size()
Out[17]: 
sex
Female     87
Male      157
dtype: int64

請注意,在我們使用的pandas代碼中size(),沒有 count()。這是因為 count()將函數(shù)應(yīng)用于每個列,返回每個列中的記錄數(shù)。not null

In [18]: tips.groupby('sex').count()
Out[18]: 
        total_bill  tip  smoker  day  time  size
sex                                             
Female          87   87      87   87    87    87
Male           157  157     157  157   157   157

或者,我們可以將該count()方法應(yīng)用于單個列:

In [19]: tips.groupby('sex')['total_bill'].count()
Out[19]: 
sex
Female     87
Male      157
Name: total_bill, dtype: int64

也可以一次應(yīng)用多個功能。例如,假設(shè)我們希望查看提示量與星期幾的不同之處 - agg()允許您將字典傳遞給分組的DataFrame,指示要應(yīng)用于特定列的函數(shù)。

SELECT day, AVG(tip), COUNT(*)
FROM tips
GROUP BY day;
/*
Fri   2.734737   19
Sat   2.993103   87
Sun   3.255132   76
Thur  2.771452   62
*/
In [20]: tips.groupby('day').agg({'tip': np.mean, 'day': np.size})
Out[20]: 
           tip  day
day                
Fri   2.734737   19
Sat   2.993103   87
Sun   3.255132   76
Thur  2.771452   62

通過將列列表傳遞給groupby()方法來完成多個列的分組 。

SELECT smoker, day, COUNT(*), AVG(tip)
FROM tips
GROUP BY smoker, day;
/*
smoker day
No     Fri      4  2.812500
       Sat     45  3.102889
       Sun     57  3.167895
       Thur    45  2.673778
Yes    Fri     15  2.714000
       Sat     42  2.875476
       Sun     19  3.516842
       Thur    17  3.030000
*/
In [21]: tips.groupby(['smoker', 'day']).agg({'tip': [np.size, np.mean]})
Out[21]: 
              tip          
             size      mean
smoker day                 
No     Fri    4.0  2.812500
       Sat   45.0  3.102889
       Sun   57.0  3.167895
       Thur  45.0  2.673778
Yes    Fri   15.0  2.714000
       Sat   42.0  2.875476
       Sun   19.0  3.516842
       Thur  17.0  3.030000

#JOIN

可以使用join()或執(zhí)行JOIN merge()。默認情況下, join()將在其索引上加入DataFrame。每個方法都有參數(shù),允許您指定要執(zhí)行的連接類型(LEFT,RIGHT,INNER,F(xiàn)ULL)或要連接的列(列名稱或索引)。

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

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

假設(shè)我們有兩個與DataFrames名稱和結(jié)構(gòu)相同的數(shù)據(jù)庫表。

現(xiàn)在讓我們來看看各種類型的JOIN。

#INNER JOIN

SELECT *
FROM df1
INNER JOIN df2
  ON df1.key = df2.key;
# merge performs an INNER JOIN by default
In [24]: pd.merge(df1, df2, on='key')
Out[24]: 
  key   value_x   value_y
0   B -0.282863  1.212112
1   D -1.135632 -0.173215
2   D -1.135632  0.119209

merge() 當您想要將一個DataFrame列與另一個DataFrame索引連接時,還會為這些情況提供參數(shù)。

In [25]: indexed_df2 = df2.set_index('key')

In [26]: pd.merge(df1, indexed_df2, left_on='key', right_index=True)
Out[26]: 
  key   value_x   value_y
1   B -0.282863  1.212112
3   D -1.135632 -0.173215
3   D -1.135632  0.119209

#LEFT OUTER JOIN

-- show all records from df1
SELECT *
FROM df1
LEFT OUTER JOIN df2
  ON df1.key = df2.key;
# show all records from df1
In [27]: pd.merge(df1, df2, on='key', how='left')
Out[27]: 
  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

#RIGHT JOIN

-- show all records from df2
SELECT *
FROM df1
RIGHT OUTER JOIN df2
  ON df1.key = df2.key;
# show all records from df2
In [28]: pd.merge(df1, df2, on='key', how='right')
Out[28]: 
  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

#FULL JOIN

pandas還允許顯示數(shù)據(jù)集兩側(cè)的FULL JOIN,無論連接列是否找到匹配項。在編寫時,所有RDBMS(MySQL)都不支持FULL JOIN。

-- show all records from both tables
SELECT *
FROM df1
FULL OUTER JOIN df2
  ON df1.key = df2.key;
# show all records from both frames
In [29]: pd.merge(df1, df2, on='key', how='outer')
Out[29]: 
  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

#UNION

UNION ALL可以使用concat()。

In [30]: df1 = pd.DataFrame({'city': ['Chicago', 'San Francisco', 'New York City'],
   ....:                     'rank': range(1, 4)})
   ....: 

In [31]: df2 = pd.DataFrame({'city': ['Chicago', 'Boston', 'Los Angeles'],
   ....:                     'rank': [1, 4, 5]})
   ....:
SELECT city, rank
FROM df1
UNION ALL
SELECT city, rank
FROM df2;
/*
         city  rank
      Chicago     1
San Francisco     2
New York City     3
      Chicago     1
       Boston     4
  Los Angeles     5
*/
In [32]: pd.concat([df1, df2])
Out[32]: 
            city  rank
0        Chicago     1
1  San Francisco     2
2  New York City     3
0        Chicago     1
1         Boston     4
2    Los Angeles     5

SQL的UNION類似于UNION ALL,但是UNION將刪除重復(fù)的行。

SELECT city, rank
FROM df1
UNION
SELECT city, rank
FROM df2;
-- notice that there is only one Chicago record this time
/*
         city  rank
      Chicago     1
San Francisco     2
New York City     3
       Boston     4
  Los Angeles     5
*/

在 pandas 中,您可以concat()結(jié)合使用 drop_duplicates()。

In [33]: pd.concat([df1, df2]).drop_duplicates()
Out[33]: 
            city  rank
0        Chicago     1
1  San Francisco     2
2  New York City     3
1         Boston     4
2    Los Angeles     5

#Pandas等同于某些SQL分析和聚合函數(shù)

#帶有偏移量的前N行

-- MySQL
SELECT * FROM tips
ORDER BY tip DESC
LIMIT 10 OFFSET 5;
In [34]: tips.nlargest(10 + 5, columns='tip').tail(10)
Out[34]: 
     total_bill   tip     sex smoker   day    time  size
183       23.17  6.50    Male    Yes   Sun  Dinner     4
214       28.17  6.50  Female    Yes   Sat  Dinner     3
47        32.40  6.00    Male     No   Sun  Dinner     4
239       29.03  5.92    Male     No   Sat  Dinner     3
88        24.71  5.85    Male     No  Thur   Lunch     2
181       23.33  5.65    Male    Yes   Sun  Dinner     2
44        30.40  5.60    Male     No   Sun  Dinner     4
52        34.81  5.20  Female     No   Sun  Dinner     4
85        34.83  5.17  Female     No  Thur   Lunch     4
211       25.89  5.16    Male    Yes   Sat  Dinner     4

#每組前N行

-- Oracle's ROW_NUMBER() analytic function
SELECT * FROM (
  SELECT
    t.*,
    ROW_NUMBER() OVER(PARTITION BY day ORDER BY total_bill DESC) AS rn
  FROM tips t
)
WHERE rn < 3
ORDER BY day, rn;
In [35]: (tips.assign(rn=tips.sort_values(['total_bill'], ascending=False)
   ....:                     .groupby(['day'])
   ....:                     .cumcount() + 1)
   ....:      .query('rn < 3')
   ....:      .sort_values(['day', 'rn']))
   ....: 
Out[35]: 
     total_bill    tip     sex smoker   day    time  size  rn
95        40.17   4.73    Male    Yes   Fri  Dinner     4   1
90        28.97   3.00    Male    Yes   Fri  Dinner     2   2
170       50.81  10.00    Male    Yes   Sat  Dinner     3   1
212       48.33   9.00    Male     No   Sat  Dinner     4   2
156       48.17   5.00    Male     No   Sun  Dinner     6   1
182       45.35   3.50    Male    Yes   Sun  Dinner     3   2
197       43.11   5.00  Female    Yes  Thur   Lunch     4   1
142       41.19   5.00    Male     No  Thur   Lunch     5   2

同樣使用 rank (method ='first') 函數(shù)

In [36]: (tips.assign(rnk=tips.groupby(['day'])['total_bill']
   ....:                      .rank(method='first', ascending=False))
   ....:      .query('rnk < 3')
   ....:      .sort_values(['day', 'rnk']))
   ....: 
Out[36]: 
     total_bill    tip     sex smoker   day    time  size  rnk
95        40.17   4.73    Male    Yes   Fri  Dinner     4  1.0
90        28.97   3.00    Male    Yes   Fri  Dinner     2  2.0
170       50.81  10.00    Male    Yes   Sat  Dinner     3  1.0
212       48.33   9.00    Male     No   Sat  Dinner     4  2.0
156       48.17   5.00    Male     No   Sun  Dinner     6  1.0
182       45.35   3.50    Male    Yes   Sun  Dinner     3  2.0
197       43.11   5.00  Female    Yes  Thur   Lunch     4  1.0
142       41.19   5.00    Male     No  Thur   Lunch     5  2.0
-- Oracle's RANK() analytic function
SELECT * FROM (
  SELECT
    t.*,
    RANK() OVER(PARTITION BY sex ORDER BY tip) AS rnk
  FROM tips t
  WHERE tip < 2
)
WHERE rnk < 3
ORDER BY sex, rnk;

讓我們找到每個性別組(等級<3)的提示(提示<2)。請注意,使用rank(method='min')函數(shù)時 rnk_min對于相同的提示保持不變 (如Oracle的RANK()函數(shù))

In [37]: (tips[tips['tip'] < 2]
   ....:     .assign(rnk_min=tips.groupby(['sex'])['tip']
   ....:                         .rank(method='min'))
   ....:     .query('rnk_min < 3')
   ....:     .sort_values(['sex', 'rnk_min']))
   ....: 
Out[37]: 
     total_bill   tip     sex smoker  day    time  size  rnk_min
67         3.07  1.00  Female    Yes  Sat  Dinner     1      1.0
92         5.75  1.00  Female    Yes  Fri  Dinner     2      1.0
111        7.25  1.00  Female     No  Sat  Dinner     1      1.0
236       12.60  1.00    Male    Yes  Sat  Dinner     2      1.0
237       32.83  1.17    Male    Yes  Sat  Dinner     2      2.0

#更新(UPDATE)

UPDATE tips
SET tip = tip*2
WHERE tip < 2;
In [38]: tips.loc[tips['tip'] < 2, 'tip'] *= 2

#刪除(DELETE)

DELETE FROM tips
WHERE tip > 9;

在pandas中,我們選擇應(yīng)保留的行,而不是刪除它們

In [39]: tips = tips.loc[tips['tip'] <= 9]

#與SAS的比較

對于來自 SAS 的潛在用戶,本節(jié)旨在演示如何在 pandas 中做各種類似SAS的操作。

由于許多潛在的 pandas 用戶對SQL有一定的了解,因此本頁面旨在提供一些使用 pandas 如何執(zhí)行各種SQL操作的示例。

如果您是 pandas 的新手,您可能需要先閱讀十分鐘入門Pandas 以熟悉本庫。

按照慣例,我們按如下方式導(dǎo)入 pandas 和 NumPy:

In [1]: import pandas as pd

In [2]: import numpy as np

注意

在本教程中,DataFrame將通過調(diào)用顯示 pandas df.head(),它將顯示該行的前N行(默認為5行)DataFrame。這通常用于交互式工作(例如Jupyter筆記本或終端) - SAS中的等價物將是:

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

#數(shù)據(jù)結(jié)構(gòu)

#一般術(shù)語對照表

PandasSAS
DataFrame數(shù)據(jù)集(data set)
column變量(variable)
row觀察(observation)
groupbyBY-group
NaN.

#DataFrame/ Series

A DataFramepandas類似于SAS數(shù)據(jù)集 - 具有標記列的二維數(shù)據(jù)源,可以是不同類型的。如本文檔所示,幾乎所有可以使用SAS DATA步驟應(yīng)用于數(shù)據(jù)集的操作也可以在pandas中完成。

A Series是表示a的一列的數(shù)據(jù)結(jié)構(gòu) DataFrame。SAS沒有針對單個列的單獨數(shù)據(jù)結(jié)構(gòu),但通常,使用a Series類似于在DATA步驟中引用列。

#Index

每一個DataFrame和Series有一個Index-這是對標簽 的行數(shù)據(jù)。SAS沒有完全類似的概念。除了在DATAstep(_N_)期間可以訪問的隱式整數(shù)索引之外,數(shù)據(jù)集的行基本上是未標記的。

在pandas中,如果未指定索引,則默認情況下也使用整數(shù)索引(第一行= 0,第二行= 1,依此類推)。雖然使用標記Index或 MultiIndex可以啟用復(fù)雜的分析,并且最終是 Pandas 理解的重要部分,但是對于這種比較,我們基本上會忽略它, Index并且只是將其DataFrame視為列的集合。有關(guān)如何有效使用的更多信息, 請參閱索引文檔Index。

#數(shù)據(jù)輸入/輸出

#從值構(gòu)造DataFrame

通過將數(shù)據(jù)放在datalines語句之后并指定列名,可以從指定值構(gòu)建SAS數(shù)據(jù)集。

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

DataFrame可以用許多不同的方式構(gòu)造一個pandas ,但是對于少量的值,通常很方便將它指定為Python字典,其中鍵是列名,值是數(shù)據(jù)。

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

#讀取外部數(shù)據(jù)

與SAS一樣,pandas提供了從多種格式讀取數(shù)據(jù)的實用程序。tips在pandas測試(csv)中找到的數(shù)據(jù)集將用于以下許多示例中。

SAS提供將csv數(shù)據(jù)讀入數(shù)據(jù)集。PROC IMPORT

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

比如,可以使用許多參數(shù)來指定數(shù)據(jù)應(yīng)該如何解析。例如,如果數(shù)據(jù)是由制表符分隔的,并且沒有列名,那么pandas命令將是:PROC IMPORT````read_csv

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還支持各種其他數(shù)據(jù)格式,如Excel,HDF5和SQL數(shù)據(jù)庫。這些都是通過pd.read_* 函數(shù)讀取的。有關(guān)更多詳細信息,請參閱IO文檔。

#導(dǎo)出數(shù)據(jù)

在SAS中proc導(dǎo)入相反就是proc導(dǎo)出

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

類似地,在 Pandas ,相反read_csv是to_csv(),與其他的數(shù)據(jù)格式遵循類似的API。

tips.to_csv('tips2.csv')

#數(shù)據(jù)操作

#列上的操作

在該DATA步驟中,可以在新列或現(xiàn)有列上使用任意數(shù)學(xué)表達式。

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

pandas 通過指定個體提供了類似的矢量化操作Series中DataFrame??梢砸韵嗤姆绞椒峙湫铝?。

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

#過濾

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可以通過多種方式進行過濾; 最直觀的是使用 布爾索引

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

#如果/那么邏輯

在SAS中,if / then邏輯可用于創(chuàng)建新列。

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

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

Pandas 中的相同操作可以使用where來自的方法來完成numpy。

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

#日期功能

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中不具備的其他時間序列功能(例如重新采樣和自定義偏移) - 有關(guān)詳細信息,請參閱時間序列文檔。

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 * 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>

#列的選擇

SAS在DATA步驟中提供關(guān)鍵字以選擇,刪除和重命名列。

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

#按值排序

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

#字符串處理

#長度

SAS使用LENGTHN 和LENGTHC 函數(shù)確定字符串的長度 。LENGTHN排除尾隨空白并LENGTHC包括尾隨空白。

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

Python使用該len函數(shù)確定字符串的長度。 len包括尾隨空白。使用len和rstrip排除尾隨空格。

In [26]: tips['time'].str.len().head()
Out[26]: 
67     6
92     6
111    6
145    5
135    5
Name: time, dtype: int64

In [27]: tips['time'].str.rstrip().str.len().head()
Out[27]: 
67     6
92     6
111    6
145    5
135    5
Name: time, dtype: int64

#查找(Find)

SAS使用FINDW函數(shù)確定字符串中字符的位置 。 FINDW獲取第一個參數(shù)定義的字符串,并搜索您提供的子字符串的第一個位置作為第二個參數(shù)。

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

Python使用find函數(shù)確定字符串中字符的位置 。 find搜索子字符串的第一個位置。如果找到子字符串,則該函數(shù)返回其位置。請記住,Python索引是從零開始的,如果找不到子串,函數(shù)將返回-1。

In [28]: tips['sex'].str.find("ale").head()
Out[28]: 
67     3
92     3
111    3
145    3
135    3
Name: sex, dtype: int64

#字符串提?。⊿ubstring)

SAS使用SUBSTR函數(shù)根據(jù)其位置從字符串中提取子字符串 。

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

使用pandas,您可以使用[]符號從位置位置提取字符串中的子字符串。請記住,Python索引是從零開始的。

In [29]: tips['sex'].str[0:1].head()
Out[29]: 
67     F
92     F
111    F
145    F
135    F
Name: sex, dtype: object

#SCAN

SAS SCAN 函數(shù)返回字符串中的第n個字。第一個參數(shù)是要解析的字符串,第二個參數(shù)指定要提取的字。

data firstlast;
input String $60.;
First_Name = scan(string, 1);
Last_Name = scan(string, -1);
datalines2;
John Smith;
Jane Cook;
;;;
run;

Python使用正則表達式根據(jù)文本從字符串中提取子字符串。有更強大的方法,但這只是一個簡單的方法。

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

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

In [32]: firstlast['Last_Name'] = firstlast['String'].str.rsplit(" ", expand=True)[0]

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

#大寫,小寫和特殊轉(zhuǎn)換

SAS UPCASE  LOWCASE和 PROPCASE 函數(shù)改變了參數(shù)的大小寫。

data firstlast;
input String $60.;
string_up = UPCASE(string);
string_low = LOWCASE(string);
string_prop = PROPCASE(string);
datalines2;
John Smith;
Jane Cook;
;;;
run;

等效Python的功能upper,lower和title。

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

In [35]: firstlast['string_up'] = firstlast['String'].str.upper()

In [36]: firstlast['string_low'] = firstlast['String'].str.lower()

In [37]: firstlast['string_prop'] = firstlast['String'].str.title()

In [38]: firstlast
Out[38]: 
       String   string_up  string_low string_prop
0  John Smith  JOHN SMITH  john smith  John Smith
1   Jane Cook   JANE COOK   jane cook   Jane Cook

#合并(Merging)

合并示例中將使用以下表格

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

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

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

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

在SAS中,必須在合并之前顯式排序數(shù)據(jù)。使用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()提供類似功能的方法。請注意,數(shù)據(jù)不必提前排序,并且通過how關(guān)鍵字可以實現(xiàn)不同的連接類型。

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

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

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

In [46]: left_join
Out[46]: 
  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 [47]: right_join = df1.merge(df2, on=['key'], how='right')

In [48]: right_join
Out[48]: 
  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 [49]: outer_join = df1.merge(df2, on=['key'], how='outer')

In [50]: outer_join
Out[50]: 
  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

#缺失數(shù)據(jù)(Missing data)

與SAS一樣,pandas具有丟失數(shù)據(jù)的表示 - 這是特殊浮點值NaN(不是數(shù)字)。許多語義都是相同的,例如,丟失的數(shù)據(jù)通過數(shù)字操作傳播,默認情況下會被聚合忽略。

In [51]: outer_join
Out[51]: 
  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 [52]: outer_join['value_x'] + outer_join['value_y']
Out[52]: 
0         NaN
1    0.929249
2         NaN
3   -1.308847
4   -1.016424
5         NaN
dtype: float64

In [53]: outer_join['value_x'].sum()
Out[53]: -3.5940742896293765

一個區(qū)別是丟失的數(shù)據(jù)無法與其哨兵值進行比較。例如,在SAS中,您可以執(zhí)行此操作以過濾缺失值。

data outer_join_nulls;
    set outer_join;
    if value_x = .;
run;

data outer_join_no_nulls;
    set outer_join;
    if value_x ^= .;
run;

這在 Pandas 中不起作用。相反,應(yīng)使用pd.isna或pd.notna函數(shù)進行比較。

In [54]: outer_join[pd.isna(outer_join['value_x'])]
Out[54]: 
  key  value_x   value_y
5   E      NaN -1.044236

In [55]: outer_join[pd.notna(outer_join['value_x'])]
Out[55]: 
  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還提供了各種方法來處理丟失的數(shù)據(jù) - 其中一些方法在SAS中表達起來很有挑戰(zhàn)性。例如,有一些方法可以刪除具有任何缺失值的所有行,使用指定值替換缺失值,例如平均值或前一行的前向填充??吹?nbsp;丟失的數(shù)據(jù)文件為多。

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

In [57]: outer_join.fillna(method='ffill')
Out[57]: 
  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 [58]: outer_join['value_x'].fillna(outer_join['value_x'].mean())
Out[58]: 
0    0.469112
1   -0.282863
2   -1.509059
3   -1.135632
4   -1.135632
5   -0.718815
Name: value_x, dtype: float64

#GroupBy

#聚合(Aggregation)

SAS的PROC SUMMARY可用于按一個或多個關(guān)鍵變量進行分組,并計算數(shù)字列上的聚合。

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

pandas提供了一種groupby允許類似聚合的靈活機制。有關(guān) 更多詳細信息和示例,請參閱groupby文檔。

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

In [60]: tips_summed.head()
Out[60]: 
               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

#轉(zhuǎn)換(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 groupby提供了一種transform機制,允許在一個操作中簡潔地表達這些類型的操作。

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

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

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

#按組處理

除了聚合之外,groupby還可以使用pandas 通過SAS的組處理來復(fù)制大多數(shù)其他pandas 。例如,此DATA步驟按性別/吸煙者組讀取數(shù)據(jù),并過濾到每個的第一個條目。

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

#其他注意事項

#磁盤與內(nèi)存

pandas僅在內(nèi)存中運行,其中SAS數(shù)據(jù)集存在于磁盤上。這意味著可以在pandas中加載的數(shù)據(jù)大小受機器內(nèi)存的限制,但對數(shù)據(jù)的操作可能更快。

如果需要進行核心處理,一種可能性是 dask.dataframe 庫(目前正在開發(fā)中),它為磁盤上的pandas功能提供了一個子集DataFrame

#數(shù)據(jù)互操作

pandas提供了一種read_sas()方法,可以讀取以XPORT或SAS7BDAT二進制格式保存的SAS數(shù)據(jù)。

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將嘗試根據(jù)其擴展名推斷文件格式。

df = pd.read_sas('transport-file.xpt', format='xport')
df = pd.read_sas('binary-file.sas7bdat', format='sas7bdat')

XPORT是一種相對有限的格式,它的解析并不像其他一些pandas讀者那樣優(yōu)化。在SAS和pandas之間交換數(shù)據(jù)的另一種方法是序列化為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

#與Stata的比較

對于來自 Stata 的潛在用戶,本節(jié)旨在演示如何在 pandas 中做各種類似Stata的操作。

如果您是 pandas 的新手,您可能需要先閱讀十分鐘入門Pandas 以熟悉本庫。

按照慣例,我們按如下方式導(dǎo)入 pandas 和 NumPy:

In [1]: import pandas as pd

In [2]: import numpy as np

注意

在本教程中,DataFrame將通過調(diào)用顯示 pandas df.head(),它將顯示該行的前N行(默認為5行)DataFrame。這通常用于交互式工作(例如Jupyter筆記本或終端) - Stata中的等價物將是:

list in 1/5

#數(shù)據(jù)結(jié)構(gòu)

#一般術(shù)語對照表

PandasStata
DataFrame數(shù)據(jù)集(data set)
column變量(variable)
row觀察(observation)
groupbybysort
NaN.

#DataFrame/ Series

pandas 中的 DataFrame 類似于 Stata 數(shù)據(jù)集-具有不同類型的標記列的二維數(shù)據(jù)源。如本文檔所示,幾乎任何可以應(yīng)用于Stata中的數(shù)據(jù)集的操作也可以在 pandas 中完成。

Series 是表示DataFrame的一列的數(shù)據(jù)結(jié)構(gòu)。Stata 對于單個列沒有單獨的數(shù)據(jù)結(jié)構(gòu),但是通常,使用 Series 類似于引用Stata中的數(shù)據(jù)集的列。

#Index

每個 DataFrame 和 Series 在數(shù)據(jù) 行 上都有一個叫 Index-label 的標簽。在 Stata 中沒有相似的概念。在Stata中,數(shù)據(jù)集的行基本上是無標簽的,除了可以用 _n 訪問的隱式整數(shù)索引。

在pandas中,如果未指定索引,則默認情況下也使用整數(shù)索引(第一行= 0,第二行= 1,依此類推)。雖然使用標記Index或 MultiIndex可以啟用復(fù)雜的分析,并且最終是 pandas 理解的重要部分,但是對于這種比較,我們基本上會忽略它, Index并且只是將其DataFrame視為列的集合。有關(guān)如何有效使用的更多信息, 請參閱索引文檔Index。

#數(shù)據(jù)輸入/輸出

#從價值觀構(gòu)建數(shù)據(jù)幀

通過將數(shù)據(jù)放在input語句之后并指定列名,可以從指定值構(gòu)建Stata數(shù)據(jù)集。

input x y
1 2
3 4
5 6
end

pandas 的 DataFrame 可以用許多不同的方式構(gòu)建,但對于少量的值,通常可以方便地將其指定為Python字典,其中鍵是列名,值是數(shù)據(jù)。

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

#讀取外部數(shù)據(jù)

與Stata一樣,pandas提供了從多種格式讀取數(shù)據(jù)的實用程序。tips在pandas測試(csv)中找到的數(shù)據(jù)集將用于以下許多示例中。

Stata提供將csv數(shù)據(jù)讀入內(nèi)存中的數(shù)據(jù)集。如果文件在當前工作目錄中,我們可以按如下方式導(dǎo)入它。import delimited````tips.csv

import delimited tips.csv

pandas 方法是read_csv()類似的。此外,如果提供了網(wǎng)址,它將自動下載數(shù)據(jù)集。

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

比如,可以使用許多參數(shù)來指定數(shù)據(jù)應(yīng)該如何解析。例如,如果數(shù)據(jù)是由制表符分隔的,沒有列名,并且存在于當前工作目錄中,則pandas命令將為:import delimitedread_csv()

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)

pandas 還可以用于 .dta 的文件格式中。使用read_stata()函數(shù)讀取格式的Stata數(shù)據(jù)集。

df = pd.read_stata('data.dta')

除了text / csv和Stata文件之外,pandas還支持各種其他數(shù)據(jù)格式,如Excel,SAS,HDF5,Parquet和SQL數(shù)據(jù)庫。這些都是通過pd.read_* 函數(shù)讀取的。有關(guān)更多詳細信息,請參閱IO文檔。

#導(dǎo)出數(shù)據(jù)

stata 中 import delimated 的反向操作是 export delimated。

export delimited tips2.csv

類似地,在 pandas 中,read_csv 的反向操作是DataFrame.to_csv()。

tips.to_csv('tips2.csv')

pandas 還可以使用DataFrame.to_stata()方法導(dǎo)出為Stata文件格式。

tips.to_stata('tips2.dta')

#數(shù)據(jù)操作

#列上的操作

在Stata中,任意數(shù)學(xué)表達式可以與新列或現(xiàn)有列上的generate和 replace命令一起使用。該drop命令從數(shù)據(jù)集中刪除列。

replace total_bill = total_bill - 2
generate new_bill = total_bill / 2
drop new_bill

pandas 通過指定個體提供了類似的矢量化操作Series中DataFrame??梢砸韵嗤姆绞椒峙湫铝?。該DataFrame.drop()方法從中刪除一列DataFrame。

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

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

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

In [11]: tips = tips.drop('new_bill', axis=1)

#過濾

在Stata中過濾是通過 if 一個或多個列上的子句完成的。

list if total_bill > 10

DataFrame可以通過多種方式進行過濾; 最直觀的是使用 布爾索引。

In [12]: tips[tips['total_bill'] > 10].head()
Out[12]: 
   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

#如果/那么邏輯

在Stata中,if子句也可用于創(chuàng)建新列。

generate bucket = "low" if total_bill < 10
replace bucket = "high" if total_bill >= 10

使用 numpy 的 where 方法可以在 pandas 中完成相同的操作。

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

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

#日期功能

Stata提供了各種函數(shù)來對date / datetime列進行操作。

generate date1 = mdy(1, 15, 2013)
generate date2 = date("Feb152015", "MDY")

generate date1_year = year(date1)
generate date2_month = month(date2)

* shift date to beginning of next month
generate date1_next = mdy(month(date1) + 1, 1, year(date1)) if month(date1) != 12
replace date1_next = mdy(1, 1, year(date1) + 1) if month(date1) == 12
generate months_between = mofd(date2) - mofd(date1)

list date1 date2 date1_year date2_month date1_next months_between

等效的 pandas 操作如下所示。除了這些功能外,pandas 還支持 Stata 中不具備的其他時間序列功能(例如時區(qū)處理和自定義偏移) - 有關(guān)詳細信息,請參閱時間序列文檔。

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

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

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

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

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

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

In [21]: tips[['date1', 'date2', 'date1_year', 'date2_month', 'date1_next',
   ....:       'months_between']].head()
   ....: 
Out[21]: 
       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>

#列的選擇

Stata 提供了選擇,刪除和重命名列的關(guān)鍵字。

keep sex total_bill tip

drop sex

rename total_bill total_bill_2

下面的 pandas 表示相同的操作。請注意,與 Stata 相比,這些操作不會發(fā)生。要使這些更改保持不變,請將操作分配回變量。

# keep
In [22]: tips[['sex', 'total_bill', 'tip']].head()
Out[22]: 
      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 [23]: tips.drop('sex', axis=1).head()
Out[23]: 
   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 [24]: tips.rename(columns={'total_bill': 'total_bill_2'}).head()
Out[24]: 
   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

#按值排序

Stata中的排序是通過 sort

sort sex total_bill

pandas 對象有一個DataFrame.sort_values()方法,它采用列表進行排序。

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

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

#字符串處理

#查找字符串的長度

Stata 分別使用ASCII和Unicode字符串 strlen() 和 ustrlen() 函數(shù)確定字符串的長度。

generate strlen_time = strlen(time)
generate ustrlen_time = ustrlen(time)

Python 使用該 len 函數(shù)確定字符串的長度。在Python 3中,所有字符串都是Unicode字符串。len包括尾隨空白。使用len和rstrip排除尾隨空格。

In [27]: tips['time'].str.len().head()
Out[27]: 
67     6
92     6
111    6
145    5
135    5
Name: time, dtype: int64

In [28]: tips['time'].str.rstrip().str.len().head()
Out[28]: 
67     6
92     6
111    6
145    5
135    5
Name: time, dtype: int64

#找到字符串的位置

Stata使用該strpos()函數(shù)確定字符串中字符的位置。這將獲取第一個參數(shù)定義的字符串,并搜索您提供的子字符串的第一個位置作為第二個參數(shù)。

generate str_position = strpos(sex, "ale")

Python使用find()函數(shù)確定字符串中字符的位置。find搜索子字符串的第一個位置。如果找到子字符串,則該函數(shù)返回其位置。請記住,Python索引是從零開始的,如果找不到子串,函數(shù)將返回-1。

In [29]: tips['sex'].str.find("ale").head()
Out[29]: 
67     3
92     3
111    3
145    3
135    3
Name: sex, dtype: int64

#按位置提取字符串

Stata根據(jù)substr()函數(shù)的位置從字符串中提取字符串。

generate short_sex = substr(sex, 1, 1)

使用pandas,您可以使用[]符號從位置位置提取字符串中的子字符串。請記住,Python索引是從零開始的。

In [30]: tips['sex'].str[0:1].head()
Out[30]: 
67     F
92     F
111    F
145    F
135    F
Name: sex, dtype: object

#提取第n個字符

Stata word()函數(shù)返回字符串中的第n個單詞。第一個參數(shù)是要解析的字符串,第二個參數(shù)指定要提取的字。

clear
input str20 string
"John Smith"
"Jane Cook"
end

generate first_name = word(name, 1)
generate last_name = word(name, -1)

Python使用正則表達式根據(jù)文本從字符串中提取字符串。有更強大的方法,但這只是一個簡單的方法。

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

In [32]: firstlast['First_Name'] = firstlast['string'].str.split(" ", expand=True)[0]

In [33]: firstlast['Last_Name'] = firstlast['string'].str.rsplit(" ", expand=True)[0]

In [34]: firstlast
Out[34]: 
       string First_Name Last_Name
0  John Smith       John      John
1   Jane Cook       Jane      Jane

#改變案例

所述的Stata strupper(),strlower(),strproper(), ustrupper(),ustrlower(),和ustrtitle()功能分別改變ASCII和Unicode字符串的情況下,。

clear
input str20 string
"John Smith"
"Jane Cook"
end

generate upper = strupper(string)
generate lower = strlower(string)
generate title = strproper(string)
list

等效Python的功能upper,lower和title。

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

In [36]: firstlast['upper'] = firstlast['string'].str.upper()

In [37]: firstlast['lower'] = firstlast['string'].str.lower()

In [38]: firstlast['title'] = firstlast['string'].str.title()

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

#合并

合并示例中將使用以下表格

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

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

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

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

在Stata中,要執(zhí)行合并,一個數(shù)據(jù)集必須在內(nèi)存中,另一個必須作為磁盤上的文件名引用。相比之下,Python必須DataFrames已經(jīng)在內(nèi)存中。

默認情況下,Stata執(zhí)行外部聯(lián)接,其中兩個數(shù)據(jù)集的所有觀察值在合并后都保留在內(nèi)存中。通過使用在_merge變量中創(chuàng)建的值,可以僅保留來自初始數(shù)據(jù)集,合并數(shù)據(jù)集或兩者的交集的觀察 。

* First create df2 and save to disk
clear
input str1 key
B
D
D
E
end
generate value = rnormal()
save df2.dta

* Now create df1 in memory
clear
input str1 key
A
B
C
D
end
generate value = rnormal()

preserve

* Left join
merge 1:n key using df2.dta
keep if _merge == 1

* Right join
restore, preserve
merge 1:n key using df2.dta
keep if _merge == 2

* Inner join
restore, preserve
merge 1:n key using df2.dta
keep if _merge == 3

* Outer join
restore
merge 1:n key using df2.dta

pandas 的 DataFrames 有一個DataFrame.merge()提供類似功能的方法。請注意,通過how關(guān)鍵字可以實現(xiàn)不同的連接類型。

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

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

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

In [47]: left_join
Out[47]: 
  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 [48]: right_join = df1.merge(df2, on=['key'], how='right')

In [49]: right_join
Out[49]: 
  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 [50]: outer_join = df1.merge(df2, on=['key'], how='outer')

In [51]: outer_join
Out[51]: 
  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

#缺少數(shù)據(jù)

像Stata一樣,pandas 有缺失數(shù)據(jù)的表示 - 特殊浮點值NaN(不是數(shù)字)。許多語義都是一樣的; 例如,丟失的數(shù)據(jù)通過數(shù)字操作傳播,默認情況下會被聚合忽略。

In [52]: outer_join
Out[52]: 
  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 [53]: outer_join['value_x'] + outer_join['value_y']
Out[53]: 
0         NaN
1    0.929249
2         NaN
3   -1.308847
4   -1.016424
5         NaN
dtype: float64

In [54]: outer_join['value_x'].sum()
Out[54]: -3.5940742896293765

一個區(qū)別是丟失的數(shù)據(jù)無法與其哨兵值進行比較。例如,在 Stata 中,您可以執(zhí)行此操作以過濾缺失值。

* Keep missing values
list if value_x == .
* Keep non-missing values
list if value_x != .

這在 pandas 中不起作用。相反,應(yīng)使用pd.isna()或pd.notna()函數(shù)進行比較。

In [55]: outer_join[pd.isna(outer_join['value_x'])]
Out[55]: 
  key  value_x   value_y
5   E      NaN -1.044236

In [56]: outer_join[pd.notna(outer_join['value_x'])]
Out[56]: 
  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 還提供了多種處理丟失數(shù)據(jù)的方法,其中一些方法在Stata中表達起來很有挑戰(zhàn)性。例如,有一些方法可以刪除具有任何缺失值的所有行,用指定值(如平均值)替換缺失值,或從前一行向前填充。有關(guān)詳細信息,請參閱缺失數(shù)據(jù)文檔。

# Drop rows with any missing value
In [57]: outer_join.dropna()
Out[57]: 
  key   value_x   value_y
1   B -0.282863  1.212112
3   D -1.135632 -0.173215
4   D -1.135632  0.119209

# Fill forwards
In [58]: outer_join.fillna(method='ffill')
Out[58]: 
  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

# Impute missing values with the mean
In [59]: outer_join['value_x'].fillna(outer_join['value_x'].mean())
Out[59]: 
0    0.469112
1   -0.282863
2   -1.509059
3   -1.135632
4   -1.135632
5   -0.718815
Name: value_x, dtype: float64

#的GroupBy

#聚合

Stata collapse可用于按一個或多個關(guān)鍵變量進行分組,并計算數(shù)字列上的聚合。

collapse (sum) total_bill tip, by(sex smoker)

pandas提供了一種groupby允許類似聚合的靈活機制。有關(guān) 更多詳細信息和示例,請參閱groupby文檔。

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

In [61]: tips_summed.head()
Out[61]: 
               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

#轉(zhuǎn)換

在Stata中,如果組聚合需要與原始數(shù)據(jù)集一起使用bysort,通常會使用egen()。例如,減去吸煙者組每次觀察的平均值。

bysort sex smoker: egen group_bill = mean(total_bill)
generate adj_total_bill = total_bill - group_bill

pandas groupby提供了一種transform機制,允許在一個操作中簡潔地表達這些類型的操作。

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

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

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

#按組處理

除聚合外,pandas groupby還可用于復(fù)制bysortStata中的大多數(shù)其他處理。例如,以下示例按性別/吸煙者組列出當前排序順序中的第一個觀察結(jié)果。

bysort sex smoker: list if _n == 1

在 pandas 中,這將寫成:

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

#其他注意事項

#磁盤與內(nèi)存

pandas 和 Stata 都只在內(nèi)存中運行。這意味著能夠在 pandas 中加載的數(shù)據(jù)大小受機器內(nèi)存的限制。如果需要進行核心處理,則有一種可能性是dask.dataframe 庫,它為磁盤上的pandas功能提供了一個子集DataFrame。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號