Pandasのよく使われる処理をSnowparkにマッピングしてみた

Snowparkが先週（2022/11/8)にGAされました。今後の使用されるユーザのために、Pandasでよく使われる関数を対象にSnowparkで書き換えてみました。

## 内容
- Pandasのチートシートを参考に、同様の処理をSnowparkにマッピングする
（もっと良い記述方法などがあれば、是非、教えて頂ければと思います！）

## 参考文献
- Pandasチートシート: https://pandas.pydata.org/Pandas_Cheat_Sheet.pdf

## 所感
- SnowparkのDataFrameが参照するデータはテーブルであるため、基本的には列方向処理がメインであり、行方向はできないケースがそこそこ存在する。例えば、loc()を使用した行指定などもできない。
- 処理内容次第ではコードの行数が長くなるので、無理にSnowparkで表現するよりもPandasで記述する or SQLで表現するなど柔軟な対応しても良いかもしれない。

## 前提
- Snowparkのバージョンは1.0を使用
- Snowparkを利用する場合、予め、Snowflakeに接続しておく必要がある（以下に接続方法を記載）

```python
from snowflake.snowpark.session import Session

CONNECTION_PARAMETERS = {
   "account": <ACCOUNT LOCATOR>,
   "user": <USERNAME>,
   "password": <PASSWORD>,
   "database": <DB>,
   "schema": <SCHEMA>,
   "warehouse": <WH>
}
session = Session.builder.configs(CONNECTION_PARAMETERS).create()
```

## PandasとSnowparkの比較

#### Creating Dataframes
まず、それぞれのDataframe向けにサンプルデータを用意
```Python: Pandas
df = pd.DataFrame(
    [[4, 7, 10],
     [5, 8, 11],
     [6, 9, 12]],
index = [1, 2, 3],
columns = ['a', 'b', 'c'])
```

```Python: Snowpark
snowpark_df = session.create_dataframe(
    [[4, 7, 10], 
     [5, 8, 11], 
     [6, 9, 12]],
schema = ['a', 'b', 'c'])
```

#### Reshaping Data
###### melt
```python: Pandas
pd.melt(df)
```
```python: Snowpark
snowpark_df.unpivot('value', 'variable', ['A', 'B', 'C']).sort('variable')
```

###### concat(列方向)
```python: Pandas
pd.concat([df, df])
```
```python: Snowpark
snowpark_df.union_all(snowpark_df)
```

###### concat(行方向)
```python: Pandas
pd.concat([df, df], axis=1)
```
```python: Snowpark
import copy
snowpark_df_2 = copy.copy(snowpark_df) # 同じ変数の使用はJoinメソッドで許可されていない
snowpark_df.join(snowpark_df_2, 'a').show()
```

###### pivot
```python: Pandas
df.pivot(columns = 'a')
```
```python: Snowpark
snowpark_df.pivot('a', [4, 5, 6]).sum('b')
```
・[4, 5, 6]と要素をハードコードしているので、これはなかなか力技である

###### sort_values(Ascending)
```python: Pandas
df.sort_values('a')
```
```python: Snowpark
snowpark_df.sort('a')
```
###### sort_values(Decending)
```python: Pandas
df.sort_values('a', ascending = False)
```
```python: Snowpark
snowpark_df.sort('a', ascending = False)
```
###### sort_index
```python: Pandas
df.sort_index()
```
```python: Snowpark
## Snowparkにindexという概念がそもそもない
```

#### Subset Observations
###### sample
```python: Pandas
df.sample(n = 2)
```
```python: Snowpark
snowpark_df.sample(n = 2)
```
###### drop_duplicates
```python: Pandas
df.drop_duplicates('a')
```
```python
snowpark_df.drop_duplicates('a')
```

###### nlargest, nsmallest
```python: Pandas
df.nlargest(2, 'a')
df.nsmallest(2, 'a')
```
```python: Snowpark
## Snowparkで直接表現できる関数はない。Rank関数とか使えばできそう
```

###### head, tail
```python: Pandas
df.head(5)
df.tail(5) 
```
```python: Snowpark
snowpark_df.show(5)
## Snowparkにtail()はない
```
- 厳密に言うと、行に対する順序という概念がないので`head()`もないのかもしれません

#### Subset Variables - columns
###### filter
```python: Pandas
## 以下、両方可能
df[['a']]
df.a
df.filter('a')
```

```python: Snowpark
from snowflake.snowpark.functions import col
## 以下、両方可能
snowpark_df.select('a')
snowpark_df.select(col('a'))
```

#### Using query
###### query
```python: Pandas
df.query('a > 4 and b > 8')
```
```python: Snowpark
snowpark_df.where('a > 4 and b > 8')
```
#### Subsets - rows and columns
###### 列指定（iloc, loc）
```python: Pandas
df.loc[:, 'b']
df.iloc[:, 1]
```
```python: Snowpark
snowpark_df.select('b')
```

###### 行指定（iloc, loc）
```python: Pandas
df.iloc[1, :]
```
```python: Snowpark
## Snowparkでは行指定が不可
```

#### Summarize Data
###### value_counts
```python: Pandas
df['a'].value_counts()
```
```python: Snowpark
snowpark_df.group_by('a').count()
```
###### nunique
```python: Pandas
print(df['a'].nunique())
```
```python: Snowpark
from snowflake.snowpark.functions import count_distinct
## 以下、両方可能
snowpark_df.select(count_distinct('a'))
snowpark_df.select('a').distinct().count()
```

###### shape, describe
```Python: Pandas
df.shape
df.describe()
```
```Python: Snowpark
snowpark_df.describe()
```
(2023/2/26に編集済み) describe（）がないと記載しておりましたが、ありましたので、修正致します。

###### sum(列方向)
```Python: Pandas
df.sum(0)
```
```python: Snowpark
from snowflake.snowpark.functions import sum
snowpark_df.select(sum('a'), sum('b'), sum('c'))
```
###### sum(行方向)
```python: Pandas
df.sum(1)
```
```python: Snowpark
snowpark_df.select(col('a') + col('b') + col('c'))
```
###### count
```python: Pandas
df.count()
```
```python: Snowpark
from snowflake.snowpark.functions import count
## 両方可能
snowpark_df.select(count('a'), count('b'), count('c'))
snowpark_df.agg([count('a'), count('b'), count('c')])
```
###### median
```python: Pandas
df.median()
```
```python: Snowpark
from snowflake.snowpark.functions import median
snowpark_df.select(median('a'), median('b'), median('c'))
```
###### quantile
```python: Pandas
df.quantile(0.25)
```
```python: Snowpark
from snowflake.snowpark.functions import percentile_cont
snowpark_df.group_by('a').agg(percentile_cont(0.25).within_group('b').as_('percentile')).sort('a')

## 余談だけど、複数の%ile値を調べたい場合に有効
snowpark_df.stat.approx_quantile('a', [0.1, 0.2, 0.5, 0.8])
```
###### apply
```python: Pandas
df.apply(lambda x: x * x)
```
```python: Snowpark
for col in snowpark_df.columns:
    snowpark_df_apply = snowpark_df.with_column(col, (snowpark_df[col] * snowpark_df[col])) 

snowpark_df_apply.show()
```
-  Snowparkにはapply関数はないので、for文で代用する必要がある

###### min
```python: Pandas
df.min()
```
```python: Snowpark
from snowflake.snowpark.functions import min
snowpark_df.agg([min('a'), min('b'), min('c')])
```
###### max
```python: Pandas
df.max()
```
```python: Snowpark
from snowflake.snowpark.functions import max
snowpark_df.agg([max('a'), max('b'), max('c')])
```
###### mean
```python: Pandas
df.mean()
```
```python: Snowpark
from snowflake.snowpark.functions import mean
snowpark_df.agg([mean('a'), mean('b'), mean('c')])
```
###### var
```python: Pandas
df.var()
```
```python: Snowpark
from snowflake.snowpark.functions import variance
snowpark_df.agg([variance('a'), variance('b'), variance('c')])
```
###### std
```python: Pandas
df.std()
```
```python: Snowpark
from snowflake.snowpark.functions import stddev
snowpark_df.agg([stddev('a'), stddev('b'), stddev('c')])
```

#### Handling Missing Data
nullを含んだデータを再作成
```python: Pandas
df_2 = pd.DataFrame(
    [[NA, 7, 10],
    [5, 8, 11],
    [6, 9, 12]],
index = [1, 2, 3],
columns = ['a', 'b', 'c'])
```
```python: Snowpark
snowpark_df_2 = session.create_dataframe(df_2)
```
・PandasデータフレームからSnowparkデータフレームに変換

###### dropna
```python: Pandas
df_2.dropna()
```
```python: Snowpark
snowpark_df_2.dropna()
```
###### fillna
```python: Pandas
df_2.fillna(100)
```
```python: Snowpark
snowpark_df_2.fillna(100)
```
#### Combine Data Sets
データをJoinするため二つデータを用意
```python: Pandas
df_left = pd.DataFrame([
    [4, 7, 10],
    [5, 8, 11],
    [6, 9, 12]],
index=[1, 2, 3],
columns=['a', 'b', 'c'])

df_right = pd.DataFrame([
    [4, 13, 16],
    [5, 14, 17],
    [7, 15, 18]],
index=[1, 2, 3],
columns=['a', 'd', 'e'])
```
```python: Snowpark
snowpark_df_left = session.create_dataframe(
    [[4, 7, 10], [5, 8, 11], [6, 9, 12]],
    schema = ['a', 'b', 'c']
)

snowpark_df_right = session.create_dataframe(
    [[4, 13, 16], [5, 14, 17], [7, 15, 18]],
    schema = ['a', 'b', 'c']
)
```
###### merge(left)
```python: Pandasg
pd.merge(df_left, df_right, how = 'left', on = 'a')
```
```python: Snowpark
snowpark_df_left.join(snowpark_df_right, 'a', join_type = 'left')
```
###### merge(right)
```python: Pandas
pd.merge(df_left, df_right, how = 'right', on = 'a')
```
```python: Snowparkg
snowpark_df_left.join(snowpark_df_right, 'a', join_type = 'right')
```
###### merge(inner)
```python: Pandas
pd.merge(df_left, df_right, on = 'a')
```
```python: Snowpark
snowpark_df_left.join(snowpark_df_right, 'a')
```
###### merge(outer)
```python: Pandas
pd.merge(df_left, df_right, how = 'outer', on = 'a')
```
```python: Snowpark
snowpark_df_left.join(snowpark_df_right, 'a', join_type = 'outer')
```
#### Group Data
Group byのためにデータを用意
```python: Pandas
df_gb = pd.DataFrame(
    [[4, 7, 10],
    [5, 8, 11],
    [6, 9, 12],
    [4, 10, 13]],
index = [1, 2, 3, 4],
columns = ['a', 'b', 'c'])
```
```python: Snowpark
snowpark_df_gb = session.create_dataframe(
    [[4, 7, 10], [5, 8, 11], [6, 9, 12], [4, 10, 13]],
    schema = ['a', 'b', 'c'])
```
###### count
```python: Pandas
df_gb.groupby(by = 'a').count()
```
```python: Snowpark
snowpark_df_gb.group_by('a').count()
```
###### size
```python: Pandas
df_gb.groupby('a').size()
```
```python: Snowpark
snowpark_df_gb.group_by('a').count()
```
###### agg
```python: Pandas
df_gb.agg(['sum', 'min'])
```
```python: Snowpark
from snowflake.snowpark.functions import sum, min
snowpark_df_gb.agg([sum('a').alias('a'), sum('b').alias('b'), sum('c').alias('c')]).union(
    snowpark_df_gb.agg([min('a'), min('b'), min('c')])).show()
```
- Snowparkではそれぞれの集約関数を実施し、結果をunionで連結する

###### shift(行方向)
```python: Pandas
df_gb.shift(-1)
```
```python: Snowpark
## Snowparkでは行追加は不可
```
###### shift(列方向)
```python: Pandas
df_gb.shift(-1, axis = 1)
```
```python: Snowpark
## Snowparkでは不可
```
###### rank
```python: Pandas
df_gb.rank(method = 'first')
```
```python: Snowpark
from snowflake.snowpark.functions import rank
from snowflake.snowpark.window import Window
order = Window.partitionBy().orderBy(col('a'))
snowpark_df.select(rank().over(order).alias('rank'), 'a', 'b', 'c')
```
- `percent_rank()`や`dense_rank()`も用意されている

###### cumsum, cummax, cummin, cumprod
```python: Snowpark
df_gb.cumsum() # 累積和
df_gb.cummax() # 累積的な最大値
df_gb.cummin() # 累積的な最小値
df_gb.cumprod() # 累積積
```
```python: Snowpark
## Snowparkでは不可
```
#### Windows
（参考文献のWindows関数は難易度が高すぎないか？もっと基礎的な関数がある気が・・・）
###### expanding
```python: Pandas
df.expanding()
```
```python: Snowpark
## Snowparkでは不可
```
###### rolling
```python: Pandas
df.rolling(n)
```
```python: Snowpark
## Snowparkでは不可
```
#### Plotting
SnowparkからPlotする場合はPandas DataFrameに変換した上でPlottingを実施
```python: Pandas
df.plot.hist()
```
```python: Snowpark
## Snowparkでは不可
```