乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

Pandas

今回はPandasの上位互換ライブラリ
Polarsのご紹介です。


解説動画はこちら


 

Polarsについて


Rustで実装された高速なデータ操作ライブラリ
Pandasに似たAPIを持ち
大規模データセットでより高速に動作するようです

Pandasの上位互換に当たりそうです。



導入方法について

Python3.7以上が必要です

Google Colabは導入済みなので
インストール不要です

インストール方法
pip install polars




大容量データの用意

Pandas , Polarsで同様のデータを使用するように
ランダム値で1000万行を用意しました。

こちらがデータ生成のコードです。
# ライブラリのインポート
import pandas as pd
import numpy as np
import polars as pl
import time

# サンプルデータを生成
np.random.seed(0)
num_rows = 10000000
names = ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank', 'Grace', 'Hannah', 'Ivy', 'Jack', 'Karen', 'Leo']
genders = ['Male', 'Female', 'Other']
prefectures = ['Hokkaido', 'Aomori', 'Iwate', 'Miyagi', 'Akita', 'Yamagata', 'Fukushima', 
               'Ibaraki', 'Tochigi', 'Gunma', 'Saitama', 'Chiba', 'Tokyo', 'Kanagawa', 
               'Niigata', 'Toyama', 'Ishikawa', 'Fukui', 'Yamanashi', 'Nagano', 'Gifu', 
               'Shizuoka', 'Aichi', 'Mie', 'Shiga', 'Kyoto', 'Osaka', 'Hyogo', 'Nara', 
               'Wakayama', 'Tottori', 'Shimane', 'Okayama', 'Hiroshima', 'Yamaguchi', 
               'Tokushima', 'Kagawa', 'Ehime', 'Kochi', 'Fukuoka', 'Saga', 'Nagasaki', 
               'Kumamoto', 'Oita', 'Miyazaki', 'Kagoshima', 'Okinawa']
favorite_foods = ['Sushi', 'Ramen', 'Tempura', 'Soba', 'Udon', 'Yakitori', 'Okonomiyaki', 
                  'Takoyaki', 'Sashimi', 'Gyoza', 'Katsudon', 'Tonkatsu']

start_date = np.datetime64('2024-01-01')
end_date = np.datetime64('2024-12-31')
date_range_days = (end_date - start_date).astype(int) + 1

data = {
    'id': np.arange(1, num_rows + 1),
    'name': np.random.choice(names, num_rows),
    'gender': np.random.choice(genders, num_rows),
    'prefecture': np.random.choice(prefectures, num_rows),
    'favorite_food': np.random.choice(favorite_foods, num_rows),
    'age': np.random.randint(18, 70, size=num_rows),
    'salary': np.random.randint(210000, 1690000, size=num_rows),
    'day': start_date + np.random.randint(0, date_range_days, size=num_rows)
}
# データフレームを生成
df = pd.DataFrame(data)

# データを保存
df.to_csv("dataset.csv", index=False)


df.shape
(10000000, 8)

こんな感じのデータになります。
スクリーンショット 2024-08-03 16.55.54


Polarsのデータの用意

# CSVファイルの読み込み
df2 = pl.read_csv("dataset.csv")

Pandasで作ったCSVをそのまま読み込みします。



PandasとPolarsの計算比較

1カラムをフィルタリングして
Groupby集計するコードです。

PandasとPolarsで掛かった秒数を計測します。
# データのフィルタリングと集計(Pandas)

# 計測開始
start_time = time.time()
result = df[df['age'] > 30].groupby('gender')['salary'].sum().reset_index()
print(result)

# 計測終了
end_time = time.time()

# 経過時間を計算
elapsed_time = end_time - start_time

print(f"経過時間: {elapsed_time} 秒")
経過時間: 3.2436938285827637 秒

# データのフィルタリングと集計(Polars)

# 計測開始
start_time = time.time()
result2 = df2.filter(pl.col("age") > 30).group_by("gender").agg(pl.sum("salary"))
print(result2)

# 計測終了
end_time = time.time()

# 経過時間を計算
elapsed_time = end_time - start_time

print(f"経過時間: {elapsed_time} 秒")
経過時間: 1.849299430847168 秒

掛かった時間は3/2くらいになっています。

今度は4つのカラムを条件にした
やや複雑な集計です。
# データのフィルタリングと集計(Pandas)

# 計測開始
start_time = time.time()

# 条件に基づいてデータをフィルタリング
average_salary = df[(df['age'] > 30) & 
                 (df['gender'] == 'Male') & 
                 (df['prefecture'] == 'Tokyo') & 
                 (df['favorite_food'] == 'Sushi')]['salary'].mean()

print(f"Average salary: {average_salary:.2f}")

# 計測終了
end_time = time.time()

# 経過時間を計算
elapsed_time = end_time - start_time

print(f"経過時間: {elapsed_time} 秒")
経過時間: 2.3793530464172363 秒

# データのフィルタリングと集計(Polars)

# 計測開始
start_time = time.time()
average_salary = df2.filter(
    (pl.col('age') > 30) & 
    (pl.col('gender') == 'Male') & 
    (pl.col('prefecture') == 'Tokyo') & 
    (pl.col('favorite_food') == 'Sushi')
)['salary'].mean()

# 結果を表示
print(f"Average salary: {average_salary:.2f}")

# 計測終了
end_time = time.time()

# 経過時間を計算
elapsed_time = end_time - start_time

print(f"経過時間: {elapsed_time} 秒")
経過時間: 0.4078397750854492 秒

こちらは6倍くらい早くなっています。


1億行くらいの計算において
5倍くらいは速いそうなので
触れ込みと同等の性能を感じられます。


まとめ

1000万行でも、条件が複雑になるほど差が出ていて
巨大なデータを扱う業界で
データ分析している人には有用なライブラリです。

そのほかにもデータの加工や
前処理も柔軟な記述が実現できたり
apply などの、加工で遅い部分を解消できるので
かなりオススメのライブラリになっています。

Pandasももう古いので
今後はPolars等が主流になっていくんだと
考えられますね

今回はPandasの上位互換である
Polarsについてお伝えしました。

それでは。



今回はPandasを高速化させられる
互換ライブラリ「FireDucks」を試してみました。

解説動画はこちら



Fireducksについて

NEC研究所さんが開発した
Pandas互換ライブラリで
Pandasデータフレームの各種処理が
高速化されている様です。

FireDucks

早速速度を試してみましょう。



インストール方法

インストールコマンドを実行するだけです。
pip install fireducks




読み込み方

import文を実行するだけで
読み込み出来ます。
import fireducks.pandas as pd

通常のPandasを読み込みする際も
省略した変数 pd を用いていると思うので
それに合わせる形にすると
既存のPandasを用いたコードに
そのまま適応できる様です。



速度検証用のコード


今回はデータフレームに対して
様々な操作を行うものを用意してみました。

100万回試行して速度比較します。
import pandas as pd
import numpy as np
import time
import fireducks.pandas as pd2

def create_data(N,t):
  data = {
        'A': np.random.randn(N),
        'B': np.random.randn(N),
        'C': np.random.choice(['O', 'P', 'P', 'A', 'Y'], N),}
  if t:
    return pd.DataFrame(data)
  else:
    return pd2.DataFrame(data)._evaluate()

# 処理時間の計測
def perform_measure(func,df,message):
    start_time = time.time()
    tmp = func(df)
    end_time = time.time()
    excute_time = end_time - start_time
    #print(f"excute {message} time : {excute_time}")
    return excute_time


# 加算処理
def df_calc(df):
    return (df['A'] + df['B'])

# 文字列置換処理
def df_replace(df):
  return df['C'].replace({"O":"1","P":"2","A":"3","Y":"4"})

# Filter処理
def df_filtering(df):
  return df[df['A'] > 0.5]

# GroupBy処理
def df_groupby(df):
  return df.groupby('C').mean()

# Sort処理
def df_sorting(df):
  return df.sort_values(by='B')

# Join処理
def df_joining(df):
    df1 = df[['A', 'B']].copy()
    df2 = df[['C']].copy()
    return df1.join(df2)

# Fillna処理
def df_fillna(df):
    df_na = df.copy()
    df_na.loc[df_na.sample(frac=0.1).index, 'A'] = np.nan
    return df_na.fillna(0)

# Dropna処理
def df_dropna(df):
    df_na = df.copy()
    df_na.loc[df_na.sample(frac=0.1).index, 'A'] = np.nan
    return df_na.dropna()


N = 1000000
df_pandas = create_data(N,True)
df_ducks = create_data(N,False)

c1 = perform_measure(df_calc,df_pandas,"calc")
c2 = perform_measure(df_calc,df_ducks,"calc")
r1 = perform_measure(df_replace,df_ducks,"replace")
r2 = perform_measure(df_replace,df_ducks,"replace")
f1 = perform_measure(df_filtering,df_pandas,"filtering")
f2 = perform_measure(df_filtering,df_ducks,"filtering")
g1 = perform_measure(df_groupby,df_pandas,"groupby")
g2 = perform_measure(df_groupby,df_ducks,"groupby")
s1 = perform_measure(df_sorting,df_pandas,"sorting")
s2 = perform_measure(df_sorting,df_ducks,"sorting")
j1 = perform_measure(df_joining,df_pandas,"joining")
j2 = perform_measure(df_joining,df_ducks,"joining")
fa1 = perform_measure(df_fillna,df_pandas,"fillna")
fa2 = perform_measure(df_fillna,df_ducks,"fillna")
da1 = perform_measure(df_dropna,df_pandas,"dropna")
da2 = perform_measure(df_dropna,df_ducks,"dropna")

data = [
  ["calc",c1,c2],
  ["replace",r1,r2],
  ["filtering",f1,f2],
  ["groupby",g1,g2],
  ["sorting",s1,s2],
  ["joining",j1,j2],
  ["fillna",fa1,fa2],
  ["dropna",da1,da2],
]

result_df = pd.DataFrame(data,columns=["処理","pandas","ducks"])
result_df["差分"] = result_df['pandas'] / result_df['ducks']
result_df

結果は動画をご覧ください。




まとめ


数値計算は高速化されているのが確認できた
文字列計算はもしかすると、あまり変わらないかも

その他の処理も速くなっている部分は有るらしいので
互換性や実行結果に問題が無ければ
高速化メリットあり、という所です。

まだベータ版っぽいので
これからもっと良くなるんだと思います。

試したい方は色々遊んでみて下さい
それでは。

今回は
DMMさんのタイトル
「出会ってXX秒で合体」シリーズで
何秒で合体するのかを
調べてみました。


解説動画はこちら



どうしても気になってしまいまして
無駄に調査しました。

データはDMMのサイトを検索して
引っかかったタイトルです。

とても紙面に載せられる感じでないので
どのようなものがあるかはDMMさんで
ご確認くださいませ。

重複などを排除したら
101タイトルありました。

その平均タイムは・・・
4.3秒!!!!!!

あっちゅう間ですねーー
玄関開けたらすぐですよね。

どんな分布になっているのでしょうか?

分布を調べてみるとこうなりました。
download

なんという偏りのある分布!!
101タイトルのうちのほとんどが
4秒という事で

大体4秒が普通になりますね。

それよりも
速い方々も少しいるようです。

4秒未満を見てみましょう。

-----------

出会って2.9秒で合体 原更紗
出会って3秒で合体 織田真子
出会って3秒即合体!!~強引セッXXが病みつきに~
出会って3秒で合体 松生彩
出会って3.1秒で合体 伊東エリ

こんな感じで5タイトル発見できました。

最速は
2.9秒:原更紗さんでした!!!!!!!!

おめでとうございます。
現在の最速は2.9秒

プロレスなら
フォール負けする前に
返せますね!!

あくまでもDMMさんの
タイトルだけですので
そこはご了承ください。

にしても
0.1秒刻みは何なんだろうか
まあ、そこを気にしていても
いいことは無さそうです。

今回はこれまで
それでは

最近投資を始める方が増えているらしいと
ニュースになっていました。

Pythonという言語は
そういった時系列データを分析するのに
非常に相性の良い言語です。

是非投資を始めるなら
プログラミングの方も
合わせて学んでいきましょう。


解説動画はこちら



今回の株価データは
こちらに掲載されていたものを
利用しています。

株式投資メモ
1321 日経225連動型上場投資信託
https://kabuoji3.com/stock/1321/

スプレッドーシートなどで
CSVファイルにする事で
株価データを読み込みする事ができます。

まずは手作業でCSVファイルを
作ってみてください。

CSVファイルが出来たら
データを読み込んでいきます。

今回は「Google Colabolatry」
というGoogle社が提供している
プログラミングツールのサービスを利用します。

https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja

Googleアカウントを持っている方は
すぐに始める事ができます。

使い方が分からない方は
こちらをみてください。
GoogleColabの使い方

まず最初にデータをアップロードします。
画面左のフォルダマークをクリックして
右クリックからアップロードで
ファイルを配置します。
スクリーンショット 2020-09-21 17.11.06


それができたらデータの読み込みを行います。

「Pandas」ライブラリは
表構造のデータを取り扱うためのライブラリです。

ファイルからデータフレームという形で
データを読み込みします。

これと数値計算用の「numpy」ライブラリ
これを使っていきます。
import pandas as pd
import numpy as np

df = pd.read_csv('stock_1321.csv',index_col=0)
df = df.sort_index(ascending=True)

df
スクリーンショット 2020-09-21 17.14.05


今回のデータはこんな感じになっています。
date 日付
open 始値
high 高値
low 安値
close 終値
volume 出来高
adjust 調整済終値

株価の動きをみていきましょう。
描画用のライブラリである
Plotlyを使って時系列データを描画します。

まずはライブラリの読み込みです。
import plotly
import plotly.graph_objs as go
# Google Colab. やJupyter Lab.でプロットするためには,以下を実行する.
import plotly.io as pio
pio.renderers.default = "colab"
plotly.__version__
4.4.1

続いて表示用データの作成です。
# 表示用のデータを生成しておく
xlabels = np.arange(len(df))
interval = 20 
vals = [df.index[i*interval] for i in range(len(df)//interval)]
labels=[df.index[i*interval] for i in range(len(df)//interval)]
date_format = 'date:{}
open:{}
high:{}
low:{}
close:{}' hovertext = [date_format.format(df.index[i],df.loc[df.index[i],'open'],df.loc[df.index[i],'high'],df.loc[df.index[i],'low'],df.loc[df.index[i],'close']) for i in range(len(df))]

表示するデータを作成できたら
描画を行います。
# ローソク足のプロット
fig = go.Figure(
    # データの指定
    data=go.Candlestick(
        x = xlabels,
        open=df['open'],
        high=df['high'],
        low=df['low'],
        close=df['close'],
        hovertext=hovertext ,
        hoverinfo="text"
        ),
    # グラフのレイアウト設定
    layout = go.Layout(
        xaxis = dict(
            tickvals=vals,
            ticktext=labels,
            tickangle=-45
        ),
    )
)
fig.show()
スクリーンショット 2020-09-21 17.17.41



データフレームには
各種データが揃っているので
ここから色々な数値計算をする事で
各指標を作成していく事ができます。

ただし、この指標の作成は
初心者には少し厳しいと思います。

cufflinksライブラリを用いると
指標の作成を簡単に行う事ができるので
これを使っていきましょう。

import cufflinks as cf

Pandasデータフレームから
一度cf用のデータに変換して描画します。
import cufflinks as cf

qf = cf.QuantFig(df, name='日経 225')

plotly.offline.iplot(
    qf.iplot(asFigure=True)
)
スクリーンショット 2020-09-21 17.20.03


ここに色々な指標を追加していきます。

移動平均(add_sma)
ボリンジャーバンド(add_bollinger_bands)
出来高(add_volume)
RSI(add_rsi)
MACD(add_macd)
qf = cf.QuantFig(df[150:], name='日経 225')

qf.add_sma([10,20],width=2,color=['green','lightgreen'],legendgroup=True)
qf.add_bollinger_bands(periods=20,boll_std=2,colors=['magenta','grey'],fill=True)
qf.add_volume()
#qf.add_rsi(periods=20,color='java')
#qf.add_macd()

plotly.offline.iplot(
    qf.iplot(asFigure=True)
)
スクリーンショット 2020-09-21 17.20.49




指標の他にラインを引く事ができます。

サポートライン
qf.add_support(日付)
抵抗ライン
qf.add_resistance(日付)

日付の指定方法が
少し特殊なので気をつけてください。

日(2桁) 月(英語表記の3桁) 年(2桁)

qf = cf.QuantFig(df[250:], name='日経 225')

# サポートライン
qf.add_support('31Jul20')

# 抵抗ライン
qf.add_resistance('25Aug20')

plotly.offline.iplot(
    qf.iplot(asFigure=True)
)
スクリーンショット 2020-09-21 17.24.20




最後にトレンドをみてみましょう。

トレンドラインを引いてみます。
qf.add_trendline(開始日, 終了日)
qf = cf.QuantFig(df[250:], name='日経 225')

qf.add_trendline('03Aug20', '26Aug20')

plotly.offline.iplot(
    qf.iplot(asFigure=True)
)
スクリーンショット 2020-09-21 17.25.14


データを差し替えれば
様々な個別銘柄の株価を描画して
トレンドなどを把握していく事ができます。

プログラミングが上達したら
各証券会社が提供しているツールよりも
細かな設定を施すことなどが出来る様になります。

自己投資として
プログラミングは
身につけておいて損はありません。

Python言語の文法などが分からない方のために
5時間ほどで学べる動画講座を用意しています。
5時間で学ぶプログラミング基礎 (python編)

是非この機会にプログラミングの方も
始めてみましょう。

分からない所があれば
気兼ねなくYoutubeにコメントをどうぞ

今回はここまでです
それでは。


 

まだまだ、コロナが収まる気配は有りませんねー

東京都の感染者のデータを見てみましょう。

解説動画はこちら

 
まずはライブラリとデータの読み込みです。

このコードでオンラインからデータを
引っ張ってくることができます。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.simplefilter('ignore')

%matplotlib inline

url = 'https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv'
# データ読み込み
df = pd.read_csv(url)

これでデータフレームにデータが取り込まれました。

データを見てみましょう。
# 先頭行
df.head(3)
# 末尾
df.tail(3)

データ量やカラムの確認も行いましょう。
# データ量
df.shape
(15107, 16)
# カラム名
df.columns
Index(['No', '全国地方公共団体コード', '都道府県名', '市区町村名', '公表_年月日', '曜日', '発症_年月日', '患者_居住地', '患者_年代', '患者_性別', '患者_属性', '患者_状態', '患者_症状', '患者_渡航歴の有無フラグ', '備考', '退院済フラグ'], dtype='object')

今日現在は2020/8/8ですが
8/7までのデータがあるようです。

使えそうなデータを見てみると
年代や、日付などが使えそうです。

年代別で集計してみましょう。
# カテゴリ別で集計
df['患者_年代'].value_counts()
20代 5169
30代 3297
40代 2041
50代 1554
60代 947
70代 768
80代 489
10代 369
10歳未満 232
90代 227
100歳以上 6
不明 6
- 2
Name: 患者_年代, dtype: int64

データ上では少し不備があり
不明なども見受けられますね。

少しだけみやすくまとめてみます。
# 年代をまとめる
df['age'] = (
    df['患者_年代'].replace(['10歳未満', '10代'], '0-19')
    .replace(['20代', '30代'], '20-39')
    .replace(['40代', '50代'], '40-59')
    .replace(['60代', '70代'], '60-79')
    .replace(['80代', '90代'], '80-99')
    .replace(['100歳以上'], '100-')
    .replace(['不明', "'-",'-'], '不明')
)

# 結果
df['age'].value_counts()
20-39    8466
40-59    3595
60-79    1715
80-99     716
0-19      601
不明          8
100-        6
Name: age, dtype: int64

感染者の多くは20,30代のようですね。

日別でも見ていきましょう。
日別でのデータ操作を行うには
日の部分の文字列を
data型に直してあげる必要があります。
# 日付をdatatime型にしてカラム追加
df['date'] = pd.to_datetime(df['公表_年月日'])

# 結果
df['date'].head()
0   2020-01-24
1   2020-01-25
2   2020-01-30
3   2020-02-13
4   2020-02-14
Name: date, dtype: datetime64[ns]

これで準備ができたので
日別での集計をしてみましょう。
# 年代別日別の感染者数集計を行う
date_df = pd.crosstab(df['date'], df['age'])

# 週別で集計し直す
day_df = date_df.resample('D', label='left').sum()
day_df.index = day_df.index.strftime('%Y-%m-%d')

# 結果
day_df.head()

# 棒グラフで描画する
day_df.plot(kind='bar',stacked=True,figsize=(12, 6))
plt.show()
download


これだとデータが細かすぎて
わけわからないですね。

週別でまとめてみます。
# 年代別日別の感染者数集計を行う
date_df = pd.crosstab(df['date'], df['age'])

# 週別で集計し直す
week_df = date_df.resample('W', label='left').sum()
week_df.index = week_df.index.strftime('%Y-%m-%d')

# 結果
week_df.head()

# 棒グラフで描画する
week_df.plot(kind='bar',stacked=True,figsize=(12, 6))
plt.show()
download-1

割とみやすくなりましたね。

続いて割合で見てみましょう。

# 割合に直したデータフレームの作成
week_rate_df = (week_df.T / week_df.sum(axis=1)).T

# 棒グラフで描画
week_rate_df.plot(kind='bar',stacked=True,figsize=(12, 6))
plt.show()
download

最初は年齢が高めの方の感染が多かったようですが
現在の主流は2-30代のようです。



ついで前週比です。
# 前週比に直したデータフレームの作成
week_df_ratio = week_df / week_df.shift()

# 折れ線グラフで描画
week_df_ratio['2020-06-07':].plot(grid=True,figsize=(12, 4))
plt.show()

download-1



ヒートマップもみてみましょう。
# 年代をまとめたカラムを追加する
df['age2'] = df['患者_年代'].replace(
    {'10歳未満': '0-9', '10代': '10-19', '20代': '20-29', '30代': '30-39', 
     '40代': '40-49', '50代': '50-59', '60代': '60-69', '70代': '70-79', 
     '80代': '80-89', '90代': '90-', '100歳以上': '90-'}
)

# 日別、年代別でヒートマップ用のデータフレームを作成する
heat_df = pd.crosstab(df['公表_年月日'], df['age2']).T[::-1]
plt.figure(figsize=(15, 4))

# seabornでヒートマップを可視化する
sns.heatmap(heat_df, cmap='nipy_spectral')
plt.show()
download-2

20代の感染が濃いですね。
やはり活動が活発な年代ほど
接触しやすくなるのでしょうか?

続いて退院フラグがあるので
それをみてみましょう。
# 退院フラグを追加
df['flg'] = df['退院済フラグ'].fillna(0).astype('int')

# 日別、フラグ別で集計したデータフレームを作成
disch_df = pd.crosstab(df['date'], df['flg'])

# 週別でリサンプリング
disch_df2 = disch_df.resample('W', label='left').sum()
disch_df2.index = disch_df2.index.strftime('%Y-%m-%d')

# 棒グラフで可視化
disch_df2.plot(kind='bar',stacked=True,figsize=(16, 5))
plt.show()
download

これでみると、早ければ2週間ほどで
退院できているようです。

中には1ヶ月以上退院できていない人もいるようなので
長期の治療を要することを
念頭に置いておかないといけませんね。


最後に移動平均です。
# 日付のデータフレームを作成
mean_df = df['date'].value_counts().sort_index()
mean_df = mean_df[30:]
fig, ax = plt.subplots(figsize=(16, 5))

# 二軸でグラフ作成
ax.bar(mean_df.index, mean_df, width=1, color='silver', edgecolor='black')
ax.plot(mean_df.index, mean_df.rolling(7).mean(), color='blue')
plt.show()
download-1


移動平均でならしても
増加傾向にあるのが分かりますね。

まだまだ油断はできません。

まあ、自分はリモートワーカーなんで
ほぼ人には接触しませんので
自粛しまくり状態ではあります。

早くワクチンなどが
開発されることを願っております。

このページのトップヘ