0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

線形補間じゃ物足りない?時系列のスムージング技術まとめてみた

Posted at

user:

時系列データの欠損値補間や、アップサンプリング、ダウンサンプリングについて、Pythonを用いて詳細かつ厳密に説明してください。

assistant:

もちろんです。Python を用いた時系列データの 欠損値補間(missing value imputation)アップサンプリング(upsampling)、および ダウンサンプリング(downsampling) について、厳密に説明します。以下では、主に pandas を使って実装していきます。


🔷 1. 時系列データの欠損値補間

時系列データにおける欠損はよくある問題です。これを補間する手法には以下のようなものがあります。

◆ 代表的な補間手法

手法 概要 Pandas関数
前方補間 (forward fill) 直前の値で埋める method='ffill'
後方補間 (backward fill) 直後の値で埋める method='bfill'
線形補間 (linear) 前後の点を直線でつないで補間 method='linear'
時系列補間 (time) 時間間隔を考慮して線形補間 method='time'
スプライン補間 曲線でなめらかに補間 method='spline', order=2
多項式補間 多項式でフィッティング method='polynomial', order=n

◆ 実装例

import pandas as pd
import numpy as np

# サンプル時系列データ(欠損あり)
rng = pd.date_range('2023-01-01', periods=10, freq='D')
data = pd.Series([1, np.nan, np.nan, 4, 5, np.nan, 7, np.nan, np.nan, 10], index=rng)

# 線形補間
data_linear = data.interpolate(method='linear')

# 時系列に基づく補間
data_time = data.interpolate(method='time')

# スプライン補間(2次)
data_spline = data.interpolate(method='spline', order=2)

🔷 2. アップサンプリング(Upsampling)

時間の粒度を細かくする操作です(例:日次 → 時間次)。通常、補間とセットで使います。

◆ 実装例

# サンプルデータ(日次)
rng = pd.date_range('2023-01-01', periods=5, freq='D')
data = pd.Series([1, 2, 3, 4, 5], index=rng)

# 時間単位にアップサンプリング(NaNが挿入される)
upsampled = data.resample('H').asfreq()

# 線形補間で埋める
upsampled_interp = upsampled.interpolate(method='linear')

◆ よく使う引数

  • .resample(freq):新しい時間粒度でグルーピング
  • .asfreq():頻度変更後に空の時間をNaNで埋める
  • .interpolate():補間で埋める

🔷 3. ダウンサンプリング(Downsampling)

時間の粒度を粗くする操作です(例:日次 → 月次)。**集約関数(sum, mean など)**と一緒に使います。

◆ 実装例

# サンプルデータ(1時間ごと)
rng = pd.date_range('2023-01-01', periods=24, freq='H')
data = pd.Series(np.random.rand(24), index=rng)

# 6時間ごとに平均を取ってダウンサンプリング
downsampled = data.resample('6H').mean()

◆ 集約関数の例

  • .mean():平均
  • .sum():合計
  • .max() / .min():最大 / 最小
  • .first() / .last():最初 / 最後の値

🔷 4. 注意点とベストプラクティス

項目 ポイント
時系列インデックス DatetimeIndexであることが必須。必要ならpd.to_datetime()で変換
欠損補間の選択 データの特性に応じて(トレンドや周期性がある場合はスプラインや時間補間)
アップサンプリング 必ず補間をセットで考える。補間しないと NaN だらけになる
ダウンサンプリング 適切な集約関数を選択することが重要(mean, sumなど)

🔷 5. 応用:時間粒度の自動判定 + 補間

def auto_resample_interpolate(series, new_freq='H'):
    resampled = series.resample(new_freq).asfreq()
    return resampled.interpolate(method='linear')

さらに、statsmodelsscipy.interpolatesklearn.impute.KNNImputerなどを使えばより高度な補間・補完も可能です。もし統計モデルをベースにした補間(例:Kalmanフィルタなど)も見てみたければ、お気軽にどうぞ!

何か特定のデータに対してやりたいことがあれば、それに合わせたコードを一緒に作ることもできます!

user:

時系列データの補完について、statsmodelsやscipy.interpolate、sklearn.impute.KNNImputerを使った補間、Kalmanフィルタなどについて、詳細かつ厳密な説明をしてください。

assistant:

了解です。ここでは、**時系列データの補完(imputation)**を、pandas 以外のライブラリ、つまり statsmodels, scipy.interpolate, sklearn.impute.KNNImputer, そして **カルマンフィルタ(Kalman Filter)**を含む統計モデルベースのアプローチを使って 詳細かつ厳密に説明します。


🔷 1. scipy.interpolate を用いた補間

scipy.interpolate は数値補間のための関数群を提供しており、時系列に対しても適用可能です。

◆ 主な関数

  • interp1d:線形、スプライン、高次補間など
  • UnivariateSpline:スムーズなスプライン補間
  • PchipInterpolator:単調性を保つ補間

◆ 実装例(線形補間)

import numpy as np
import pandas as pd
from scipy.interpolate import interp1d

# 欠損ありのデータ
rng = pd.date_range('2023-01-01', periods=10, freq='D')
y = np.array([1, np.nan, np.nan, 4, 5, np.nan, 7, np.nan, np.nan, 10])
ts = pd.Series(y, index=rng)

# 補間対象を除外して補間関数を作成
valid = ts.dropna()
f = interp1d(valid.index.astype(np.int64), valid.values, kind='linear', fill_value="extrapolate")

# 補間実行
interpolated = pd.Series(f(ts.index.astype(np.int64)), index=ts.index)

⚠️ 注意: astype(np.int64)datetime64 を数値に変換(ナノ秒)。このまま使うと将来の NumPy で警告が出るので view('int64')timestamp() に置き換えるのが安全です。


🔷 2. sklearn.impute.KNNImputer を用いた補間

KNNImputer時系列の時間依存構造を無視し、特徴量空間上の近傍情報に基づいて補完します。これはセンサーデータや多次元系列に有効です。

◆ 実装例(1次元データでも適用可能)

import numpy as np
import pandas as pd
from sklearn.impute import KNNImputer

# 欠損を含むデータ(ここでは 1D → 2D に reshape)
data = np.array([1, np.nan, np.nan, 4, 5, np.nan, 7, np.nan, np.nan, 10])
data = data.reshape(-1, 1)

imputer = KNNImputer(n_neighbors=2, weights="distance")
imputed_data = imputer.fit_transform(data)

# Series に戻す
ts_imputed = pd.Series(imputed_data.ravel())

✔ 時系列順が保持されている必要がありますが、時間情報自体は考慮されません。


🔷 3. statsmodels を用いた補間(線形回帰・トレンドモデルなど)

statsmodels では、回帰モデル・時系列モデルをフィットさせて補間ができます。例えば、線形トレンドに基づく予測補完などが可能です。

◆ 実装例:線形トレンドによる補間

import pandas as pd
import numpy as np
import statsmodels.api as sm

# 欠損を含む時系列
rng = pd.date_range('2023-01-01', periods=10, freq='D')
y = np.array([1, np.nan, np.nan, 4, 5, np.nan, 7, np.nan, np.nan, 10])
ts = pd.Series(y, index=rng)

# 欠損なしのデータで回帰モデルを学習
df = ts.reset_index()
df.columns = ['date', 'value']
df['time'] = np.arange(len(df))
valid = df.dropna()
X = sm.add_constant(valid['time'])
model = sm.OLS(valid['value'], X).fit()

# 欠損部分を予測
X_all = sm.add_constant(df['time'])
predicted = model.predict(X_all)
ts_filled = pd.Series(predicted, index=rng)

🔷 4. カルマンフィルタによる補間(statsmodels.tsa.statespace

カルマンフィルタ(Kalman Filter)は、状態空間モデルをベースとした動的系列の補間・予測手法です。

◆ モデル選択例

  • ローカルレベルモデル:UnobservedComponents(level='local level')
  • ローカルトレンドモデル:level='local linear trend'

◆ 実装例(ローカルレベルモデル)

from statsmodels.tsa.statespace.structural import UnobservedComponents

# 欠損を含むデータ
rng = pd.date_range('2023-01-01', periods=15, freq='D')
data = np.array([1, 2, np.nan, 4, 5, 5.5, np.nan, 6, 6.5, np.nan, 7, 7.2, np.nan, np.nan, 8])
ts = pd.Series(data, index=rng)

# 状態空間モデル(カルマンフィルタ)でフィット
mod = UnobservedComponents(ts, level='local level')
res = mod.fit(disp=False)

# フィルタリング(= 補間)
ts_kalman = res.filtered_state[0]
ts_kalman = pd.Series(ts_kalman, index=rng)

◆ 特徴

特徴 説明
モデルベース ノイズやトレンドの分離が可能
補間精度 ノイズの多い時系列にも強い
パラメータ調整 状態数、観測ノイズの分散、トレンド構造などを調整可能

🔶 各手法の比較

手法 時間考慮 ノイズに強い 複雑性 特徴
scipy.interpolate △(明示的に時刻変換) 柔軟な補間関数あり
KNNImputer × 時系列構造を無視
statsmodels.OLS トレンド前提の補間
カルマンフィルタ 状態空間で最適補間

🧪 実データでやってみたいとき

  • 欠損が 不連続・小規模scipy.interpolate, ffill
  • 欠損が 広範囲・ノイズ含む → カルマンフィルタ
  • 複数センサーなど 多次元データKNNImputer
  • トレンドが明らかな場合 → statsmodels の回帰

もし補間結果の可視化比較などもご希望なら、それにあわせたコードも用意できます!また、ARIMA や LSTM を使った予測ベースの補完についても対応可能です。興味ありますか?

user:

ありがとうございます。時系列データの欠損に対するさまざまな補間方法に対して、補間結果の可視化比較ができるPythonコードを書いてください。

assistant:

どういたしまして!
以下に、時系列データの欠損補間結果を可視化比較できる Python コードを提供します。


✅ このコードでできること

  • 人為的に欠損を含む時系列データを生成
  • 以下の補間手法で補間:
    • pandas.interpolate(method='linear')
    • scipy.interpolate.interp1d
    • sklearn.impute.KNNImputer
    • statsmodels を用いた線形トレンド補完
    • カルマンフィルタ(UnobservedComponents
  • 元データ・欠損・補間結果を一つのグラフにプロット

📌 コード

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from scipy.interpolate import interp1d
from sklearn.impute import KNNImputer
import statsmodels.api as sm
from statsmodels.tsa.statespace.structural import UnobservedComponents

# ---------- 1. データ作成 ----------
np.random.seed(42)
rng = pd.date_range('2023-01-01', periods=30, freq='D')
data_true = np.sin(np.linspace(0, 3 * np.pi, 30)) + np.random.normal(0, 0.1, 30)

# ランダムに欠損を挿入
data_missing = data_true.copy()
missing_indices = np.random.choice(range(30), size=10, replace=False)
data_missing[missing_indices] = np.nan
ts_missing = pd.Series(data_missing, index=rng)

# ---------- 2. 各種補間手法 ----------

# 2.1 Pandas: 線形補間
pandas_interp = ts_missing.interpolate(method='linear')

# 2.2 Scipy: interp1d
valid = ts_missing.dropna()
f_scipy = interp1d(valid.index.astype(np.int64), valid.values, kind='linear', fill_value="extrapolate")
scipy_interp = pd.Series(f_scipy(ts_missing.index.astype(np.int64)), index=rng)

# 2.3 KNNImputer
knn_data = ts_missing.values.reshape(-1, 1)
knn_imputed = KNNImputer(n_neighbors=2).fit_transform(knn_data).ravel()
knn_interp = pd.Series(knn_imputed, index=rng)

# 2.4 statsmodels: 線形回帰
df = ts_missing.reset_index()
df.columns = ['date', 'value']
df['time'] = np.arange(len(df))
valid_df = df.dropna()
X_valid = sm.add_constant(valid_df['time'])
ols_model = sm.OLS(valid_df['value'], X_valid).fit()
X_all = sm.add_constant(df['time'])
ols_pred = ols_model.predict(X_all)
ols_interp = pd.Series(ols_pred, index=rng)

# 2.5 カルマンフィルタ(UnobservedComponents)
kalman_model = UnobservedComponents(ts_missing, level='local level')
kalman_res = kalman_model.fit(disp=False)
kalman_interp = pd.Series(kalman_res.filtered_state[0], index=rng)

# ---------- 3. 可視化 ----------
plt.figure(figsize=(14, 8))

plt.plot(rng, data_true, label='True Data', color='black', linewidth=2, linestyle='--', alpha=0.5)
plt.scatter(rng[missing_indices], data_true[missing_indices], label='Missing', color='red', zorder=5)

plt.plot(pandas_interp, label='Pandas Linear', color='blue')
plt.plot(scipy_interp, label='Scipy interp1d', color='orange')
plt.plot(knn_interp, label='KNNImputer', color='green')
plt.plot(ols_interp, label='Statsmodels OLS', color='purple')
plt.plot(kalman_interp, label='Kalman Filter', color='brown')

plt.title('Time Series Imputation Methods Comparison')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

🔍 グラフの解釈ポイント

  • 破線の黒:真の値(参考)
  • 赤い点:欠損部分(元データは存在)
  • 各手法のライン:補間結果

必要に応じて:

  • 他の補間手法(スプライン、ポリノミアルなど)の追加
  • 多次元時系列での適用
  • RMSE などの 定量評価の導入
    なども可能です。お気軽にどうぞ!

user:

年次データから月次データにアップサンプリングする方法は説明できますか?

assistant:

もちろんです!
年次データから月次データにアップサンプリング(up-sampling)する方法は、時系列データの頻度を高くする処理の一つです。たとえば、年に1つの観測値を、各月のデータに補間して埋めるというイメージです。


🔷 年次→月次のアップサンプリングとは?

項目 内容
元データ 年単位の時系列(例:2020年、2021年、2022年)
変換後 月単位の時系列(例:2020-01, 2020-02, ..., 2022-12)
目的 詳細な分析・補間・モデルフィッティングなど
注意点 補間方法を適切に選ぶ必要がある(直前値、線形補間など)

🔸 実装例(pandas を使用)

▶ 年次データを作成

import pandas as pd
import numpy as np

# 年次データ(例)
data = pd.Series(
    [100, 120, 140],
    index=pd.to_datetime(['2020', '2021', '2022'])
)
print(data)

▶ 月次にアップサンプリング(リサンプリング)+補間

# 月次にアップサンプリング
monthly = data.resample('MS').asfreq()  # 'MS' = 月初

# 線形補間
monthly_interp = monthly.interpolate(method='linear')

print(monthly_interp)

🔸 結果イメージ

日付
2020-01-01 100.0
2020-02-01 101.7
2020-03-01 103.3
... ...
2022-12-01 140.0

🔸 補間の選択肢

# 線形補間
monthly.interpolate(method='linear')

# 時間補間(日時を考慮した線形補間)
monthly.interpolate(method='time')

# 前の値で埋める(ステップ状)
monthly.ffill()

# スプライン補間(より滑らか)
monthly.interpolate(method='spline', order=2)

🔶 グラフで確認(補間の可視化)

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 5))
plt.plot(data, 'o', label='Yearly Data')
plt.plot(monthly_interp, '-', label='Monthly Interpolated')
plt.title('Upsampling: Yearly to Monthly')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.show()

📝 まとめ

操作 関数・方法
周期の変更(年→月) resample('MS').asfreq()
補間 interpolate(method='linear')など
グラフ matplotlib.pyplot

補間の精度や妥当性はデータの性質に依存するので、例えば経済指標のように季節性があるデータでは、単純な線形補間よりも季節調整モデル(例:ETS、SARIMA)やスプライン補間の方が自然な場合もあります。

必要があれば、そういったより高度なモデルによる月次補間の実装もご紹介できますよ!興味ありますか?

user:

季節調整モデルを用いた月次補間をPythonで説明してください。

assistant:

了解です!
ここでは、年次データを月次にアップサンプリングした後に、季節調整モデル(例:SARIMAや状態空間モデル)を用いて月次の値を自然な形で補間する方法をPythonで丁寧に説明します。


✅ なぜ季節調整モデルを使うのか?

単純な線形補間だと、季節性(例:夏に売上が増える、冬に減るなど)を表現できません。
**季節調整モデル(例:SARIMA, UnobservedComponents)**を使うことで、トレンド+季節性+誤差を分解して、より自然な補間ができます。


🔸 手順の全体像

  1. 年次データを用意する
  2. 月次にアップサンプリングする(resample('MS')
  3. 季節調整モデル(SARIMA or UnobservedComponents)を学習
  4. 欠損を含む時系列全体を補間する(予測 or フィルタリング)

🔷 コード例:UnobservedComponents モデルでの月次補間

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.tsa.statespace.structural import UnobservedComponents

# 1. 年次データを作成
years = pd.date_range('2015-01-01', periods=5, freq='AS')  # 年初ベース
values = pd.Series([100, 130, 110, 140, 120], index=years)

# 2. 月次にアップサンプリング(欠損を持つ時系列)
monthly = values.resample('MS').asfreq()  # 月初(Monthly Start)
print(monthly.head(12))

# 3. モデル構築:ローカルトレンド + 季節性(12ヶ月周期)
mod = UnobservedComponents(monthly, level='local level', seasonal=12)

# 4. フィッティング(EMアルゴリズム的に欠損も処理される)
res = mod.fit(disp=False)

# 5. 補間結果(フィルタリングされた系列 = 欠損が補間された系列)
monthly_filled = pd.Series(res.filtered_state[0], index=monthly.index)

# 6. 可視化
plt.figure(figsize=(12, 6))
plt.plot(values, 'o-', label='Yearly Data (Observed)')
plt.plot(monthly_filled, '-', label='Monthly Interpolated (Seasonal Model)', color='tab:orange')
plt.title("Upsampling Yearly Data to Monthly Using Seasonal State-Space Model")
plt.xlabel("Date")
plt.ylabel("Value")
plt.legend()
plt.grid(True)
plt.show()

🔍 結果の説明

  • values: 年に一度だけ観測された実データ
  • monthly: 欠損を含む月次データ
  • monthly_filled: 状態空間モデルにより補間された月次データ

UnobservedComponentsモデルは、トレンドや季節性を内部的に考慮して補間してくれるため、以下のような補間になります:

  • 冬に値が下がる・夏に上がるようなパターンが再現される
  • 単なる直線ではなく、滑らかに変化する補間になる

🧠 モデルパラメータ補足

UnobservedComponents(
    endog, 
    level='local level', 
    seasonal=12,  # 12ヶ月周期
    freq_seasonal=[{'period': 12, 'harmonics': 3}],
    irregular=True
)

これにより、より柔軟な補間も可能です。


✅ まとめ

補間手法 特徴
線形補間 単純・早いが季節性は無視される
スプライン補間 なめらかだが外挿に弱い
状態空間モデル 季節性・トレンド・ノイズを同時にモデル化できる
SARIMA補間 明示的な季節性ARIMA構造が必要、より厳密な時系列モデリング

ご希望であれば、SARIMA を用いた補間バージョンや、季節トレンド分解(STL)で補間後に再構成する方法もご紹介できます!興味ありますか?

user:

SARIMA を用いた補間バージョンや、季節トレンド分解(STL)で補間後に再構成する方法も教えてください。

assistant:

よろこんで!
ここでは以下の2つの方法を Python で 丁寧に解説&コード付きで紹介します:


✅ 1. SARIMA を用いた補間(アップサンプリング後の予測による補間)

🔹 概要

SARIMA(季節性ARIMA)モデルは、自己回帰・移動平均成分に加え、季節性の成分も扱える強力な時系列モデルです。


▶ ステップ:

  1. 年次データ → 月次データへアップサンプリング(欠損含む)
  2. モデルを適合(SARIMA)
  3. predict() または forecast() で月次補間を取得

🔸 コード例(SARIMA補間)

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.statespace.sarimax import SARIMAX

# 1. 年次データ
years = pd.date_range('2015-01-01', periods=5, freq='AS')
data = pd.Series([100, 130, 110, 140, 120], index=years)

# 2. 月次へアップサンプリング(欠損あり)
monthly = data.resample('MS').asfreq()

# 3. SARIMA モデルの適合(パラメータは簡単化:要チューニング可能)
model = SARIMAX(monthly, order=(1,1,1), seasonal_order=(1,1,0,12), enforce_stationarity=False, enforce_invertibility=False)
results = model.fit(disp=False)

# 4. 補間:インサンプル予測
monthly_pred = results.predict(start=monthly.index[0], end=monthly.index[-1])

# 5. 可視化
plt.figure(figsize=(12,6))
plt.plot(data, 'o-', label='Yearly Observed')
plt.plot(monthly_pred, '-', color='orange', label='SARIMA Monthly Interpolated')
plt.grid(True)
plt.legend()
plt.title("Upsampling with SARIMA Interpolation")
plt.xlabel("Date")
plt.ylabel("Value")
plt.show()

✅ 2. STL(季節・トレンド分解)+スプライン補間による再構成

🔹 概要

STL(Seasonal-Trend decomposition using Loess)で:

  • トレンド
  • 季節性
  • 残差

に分解し、それぞれを補間 → 最後に和で復元するアプローチです。


🔸 コード例(STL補間)

from statsmodels.tsa.seasonal import STL
from scipy.interpolate import UnivariateSpline

# 1. 年次データ → 月次にアップサンプリング
monthly = data.resample('MS').asfreq()

# 2. 欠損を持ったまま STL 分解(dropna=True で学習用のみ)
stl = STL(monthly.dropna(), seasonal=13)
res = stl.fit()

# 3. スプライン補間で各成分を月次に復元
idx = monthly.dropna().index
all_idx = monthly.index

# スプライン補間関数(3次)
def spline_interp(series):
    return pd.Series(UnivariateSpline(idx, series, s=0)(all_idx), index=all_idx)

seasonal_interp = spline_interp(res.seasonal)
trend_interp = spline_interp(res.trend)
resid_interp = spline_interp(res.resid)

# 4. 合成: trend + seasonal + residual
stl_interp = trend_interp + seasonal_interp + resid_interp

# 5. 可視化
plt.figure(figsize=(12,6))
plt.plot(data, 'o-', label='Yearly Data')
plt.plot(stl_interp, label='STL + Spline Interpolation', color='tab:green')
plt.grid(True)
plt.legend()
plt.title("STL-Based Seasonal Interpolation of Yearly Data")
plt.xlabel("Date")
plt.ylabel("Value")
plt.show()

🧠 比較まとめ

手法 モデル 季節性考慮 補間の滑らかさ 注意点
SARIMA 統計モデル △(ステップ気味) パラメータ推定が重要
STL + 補間 分解 + スプライン ◎(スムーズ) 季節性周期の指定が必要

📝 最後に

どちらの方法も「アップサンプリング+補間」として有効で、

  • SARIMAは「モデルベース補間」(予測含む)
  • STLは「分解+補間」のハイブリッド方式

です。
必要なら、prophetGaussianProcessなどのより柔軟な補間モデルの紹介もできますよ!

興味があれば、どれを深掘りしたいか教えてください!

user:

prophetやGaussianProcessなどのより柔軟な補間モデルを教えてください。

assistant:

いいですね、さらに一歩進んだ「柔軟な時系列補間手法」として、以下の2つを詳しく解説します!


✅ 1. Facebook Prophet による補間

🔹 特徴

  • トレンド+季節性+休日効果を自動的にモデリング
  • 欠損値に強く、非等間隔データでもOK
  • 未来予測も簡単
  • パラメータチューニング不要で黒魔法感があるが実用的

▶ コード例:Prophet で年次→月次補間

import pandas as pd
import numpy as np
from prophet import Prophet
import matplotlib.pyplot as plt

# 1. 年次データ
years = pd.date_range('2015-01-01', periods=5, freq='AS')
data = pd.Series([100, 130, 110, 140, 120], index=years)

# 2. Prophet 用に変換
df = pd.DataFrame({
    'ds': data.index,
    'y': data.values
})

# 3. モデル学習
model = Prophet(yearly_seasonality=True, daily_seasonality=False, weekly_seasonality=False)
model.fit(df)

# 4. 月次補間(将来分も含む月単位のフレームを作る)
future = model.make_future_dataframe(periods=0, freq='MS')
monthly_range = pd.date_range(start=years[0], end=years[-1], freq='MS')
future = pd.DataFrame({'ds': monthly_range})

# 5. 予測(補間)
forecast = model.predict(future)

# 6. 可視化
plt.figure(figsize=(12,6))
plt.plot(data.index, data.values, 'o-', label='Yearly Data')
plt.plot(forecast['ds'], forecast['yhat'], '-', color='tab:blue', label='Prophet Monthly Interpolated')
plt.grid(True)
plt.title("Interpolation using Prophet")
plt.xlabel("Date")
plt.ylabel("Value")
plt.legend()
plt.show()

✅ 2. Gaussian Process Regression(GPR)による補間

🔹 特徴

  • ベイズ的回帰手法で、不確実性も出せる
  • 非線形、滑らかな関数補間が得意
  • 時系列で使えば補間にも予測にも超強力
  • 様々なカーネル関数で構造を表現可能(RBF, Periodic, etc.)

▶ コード例:GPR による滑らかな時系列補間

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, ExpSineSquared, WhiteKernel

# 年次データ
years = pd.date_range('2015-01-01', periods=5, freq='AS')
x_train = np.array([(d - years[0]).days for d in years]).reshape(-1, 1)
y_train = np.array([100, 130, 110, 140, 120])

# 月次での補間用 X
monthly_dates = pd.date_range('2015-01-01', '2019-12-01', freq='MS')
x_test = np.array([(d - years[0]).days for d in monthly_dates]).reshape(-1, 1)

# カーネル(周期性+滑らかさ)
kernel = ExpSineSquared(length_scale=365.0, periodicity=365.25) + RBF(length_scale=500) + WhiteKernel()

# モデル学習
gpr = GaussianProcessRegressor(kernel=kernel, alpha=1e-2)
gpr.fit(x_train, y_train)

# 補間&不確実性推定
y_pred, sigma = gpr.predict(x_test, return_std=True)

# 可視化
plt.figure(figsize=(12,6))
plt.plot(years, y_train, 'o', label='Yearly Observed')
plt.plot(monthly_dates, y_pred, '-', label='GPR Interpolation', color='green')
plt.fill_between(monthly_dates, y_pred - sigma, y_pred + sigma, color='green', alpha=0.2, label='Confidence Interval')
plt.title("Gaussian Process Interpolation")
plt.xlabel("Date")
plt.ylabel("Value")
plt.legend()
plt.grid(True)
plt.show()

✅ 比較まとめ

モデル 特徴 補間精度 解釈性 実装難易度
Prophet トレンド・季節性・休日の組合せ 中(ブラックボックス寄り) 低(簡単)
GPR ベイズ的・非線形・不確実性あり 非常に高 高(カーネル構造が意味を持つ) 中〜高

🎁 どれを選ぶべき?

  • 簡単&実用的に滑らかな補間がしたい → Prophet ✅
  • より理論的で柔軟・不確実性も重要 → GPR ✅
  • モデルの中身や挙動を完全に理解したい → GPR や STL ✅

他にも「多変量時系列での補間」や「DeepAR、NeuralProphet」のようなディープラーニングベースの補間もあります。
必要に応じてご紹介できますので、お気軽にどうぞ!

user:

from statsmodels.tsa.statespace.structural import UnobservedComponents について詳しく教えていただけますか。

assistant:

もちろんです!statsmodels.tsa.statespace.structural.UnobservedComponents(略して UCM)は、**構造時系列モデル(Structural Time Series Model)**を実装するための非常に強力なクラスです。


🔍 UnobservedComponentsとは?

これは「状態空間モデル」の一種であり、観測された時系列データを以下のような構成要素の合成でモデル化します:

  • トレンド(level, slope)
  • 季節性(seasonal)
  • 回帰成分(exogenous variables)
  • 周期性(cycle)
  • 誤差(irregular / noise)

🧠 数式的イメージ

観測値 ( y_t ) は以下のように構成されます:

[
y_t = \mu_t + \gamma_t + \psi_t + \varepsilon_t
]

  • ( \mu_t ): トレンド成分(局所線形トレンドなど)
  • ( \gamma_t ): 季節成分(周期的なパターン)
  • ( \psi_t ): 周期性(例えば景気循環)
  • ( \varepsilon_t ): ノイズ(ホワイトノイズ)

✅ 使用例と構文

from statsmodels.tsa.statespace.structural import UnobservedComponents
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 例: 季節性 + トレンドを含む人工データ
np.random.seed(0)
date_idx = pd.date_range(start='2010-01', periods=120, freq='M')
trend = np.linspace(10, 20, 120)
seasonal = 2 * np.sin(np.linspace(0, 12*np.pi, 120))
noise = np.random.normal(0, 0.5, 120)
y = trend + seasonal + noise

data = pd.Series(y, index=date_idx)

# モデル定義:局所線形トレンド + 季節性(12ヶ月)
mod = UnobservedComponents(data, level='local linear trend', seasonal=12)
res = mod.fit()

# プロット
res.plot_components(legend_loc='lower right', figsize=(12,8))
plt.tight_layout()
plt.show()

🧩 指定可能なコンポーネント

コンポーネント 説明 指定方法
level トレンドの水準 'local level' or 'local linear trend'
trend 傾きの変動(level='local linear trend'で含まれる)
seasonal 季節性(指定は周期数) seasonal=12(月次の場合)
cycle 経済循環などの周期成分 cycle=True
irregular ランダムノイズ 省略可(デフォルトで含まれる)
exog 外生変数 exog=外部変数配列

🔧 トレンドの種類(levelの指定)

level の値 含意
'fixed intercept' 定数トレンド
'local level' ノイズを持つレベルのみ
'local linear trend' レベルと傾きにノイズ
'random walk with drift' 定常でないドリフト付きランダムウォーク

📊 補間・予測・成分の分解

1. 欠損値補間

data_missing = data.copy()
data_missing[10:20] = np.nan
model = UnobservedComponents(data_missing, level='local linear trend', seasonal=12)
results = model.fit()
filled = results.predict()

2. 成分表示

results.plot_components()

3. 未来予測

pred = results.get_forecast(steps=12)
pred_ci = pred.conf_int()

🧠 UnobservedComponents の使い所

  • 欠損値補間(時系列構造を考慮した補間)
  • トレンド・季節性・サイクルの分離
  • 説明変数付きのモデル化(exog
  • スムージングや未来予測

🧠 ARIMAとの違い

特徴 ARIMA UnobservedComponents
モデル形式 算術的(差分) 構成的(成分合成)
季節性 SARIMAで処理 明示的に分離・推定可能
欠損値補間 × ◎(状態空間モデル)
柔軟性
可視化 ◎(トレンド・季節性・残差を分離)

📚 補足ドキュメント


必要であれば:

  • 欠損補間の具体例
  • exog 付きの外生変数の例
  • モデル選択(AIC/BIC)やフィルタの出力
    などもご紹介できます!

どの点を深掘りしたいですか?

user:

年次データを月次データに補間するとき、線形補間だとカクカクになってしまうので、線形補完したものからさらに移動平均を取ることで滑らかな補間はできませんかね?Pythonで実装してみて可視化していただけませんか?

assistant:

できます!年次データを単純に線形補間すると、各年の値を直線的に結ぶだけなので、カクカクした見た目になります。
そこで、線形補間後に移動平均(smoothing)を適用することで、より滑らかにする方法はとても理にかなっています。


🧪 やることの概要

  1. 年次データを月次にアップサンプリング
  2. 線形補間で補完
  3. 移動平均(ローパスフィルタ)を適用
  4. 元のデータ、補間値、スムージング結果を可視化

✅ 実装コード

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 1. 年次データ(例)
years = pd.date_range('2015-01-01', periods=6, freq='AS')
values = pd.Series([100, 130, 120, 150, 140, 160], index=years)

# 2. 月次にアップサンプリング
monthly = values.resample('MS').asfreq()

# 3. 線形補間
linear_interp = monthly.interpolate(method='linear')

# 4. 移動平均でスムージング(±2ヶ月程度)
smooth = linear_interp.rolling(window=5, center=True, min_periods=1).mean()

# 5. 可視化
plt.figure(figsize=(14, 6))
plt.plot(values, 'o-', label='Yearly Data', linewidth=2)
plt.plot(linear_interp, '--', label='Linear Interpolation', alpha=0.7)
plt.plot(smooth, '-', label='Smoothed (Moving Average)', linewidth=2, color='green')
plt.title('Upsampling Yearly to Monthly: Linear Interpolation + Moving Average')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

🔍 補足説明

処理 内容
.resample('MS').asfreq() 月初基準でアップサンプリング(欠損はNaN)
.interpolate(method='linear') 欠損を線形で補完
.rolling(window=5, center=True) 5ヶ月ウィンドウの移動平均(左右2ヶ月+中心)

📈 出力結果のイメージ(テキスト)

  • 青丸:元の年次データ
  • 点線:単純な線形補間(角ばっている)
  • 緑線:移動平均を加えた補間結果(滑らか)

✨ 応用アイデア

  • rollingの代わりに scipy.signal.savgol_filter を使うともっとなめらかに(Savitzky-Golay 平滑化)
  • seasonal_decompose や STL を使ってトレンドだけ抽出して平滑化する方法もあり

気に入っていただけたら、Savitzky-Golay バージョンも作ってみましょうか?

user:

Savitzky-Golay バージョンも作ってください。

assistant:

もちろんです!Savitzky-Golay フィルターは、移動平均よりも滑らかかつトレンドを保った補間が可能な「ローパス平滑化法」です。scipy.signal.savgol_filter を使って、線形補間後のデータをさらになめらかにできます。


✅ Savitzky-Golay フィルターによるスムージング

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import savgol_filter

# 1. 年次データ(例)
years = pd.date_range('2015-01-01', periods=6, freq='AS')
values = pd.Series([100, 130, 120, 150, 140, 160], index=years)

# 2. 月次にアップサンプリング
monthly = values.resample('MS').asfreq()

# 3. 線形補間
linear_interp = monthly.interpolate(method='linear')

# 4. Savitzky-Golay フィルターでスムージング
# window_length は奇数、polyorder は多項式の次数(通常は2 or 3)
window_length = 11  # 約1年以内の範囲をスムージング
polyorder = 2
# NaNがあるとエラーになるので、欠損がないことを確認してから
smooth_savgol = pd.Series(
    savgol_filter(linear_interp.values, window_length=window_length, polyorder=polyorder, mode='interp'),
    index=linear_interp.index
)

# 5. 可視化
plt.figure(figsize=(14, 6))
plt.plot(values, 'o-', label='Yearly Data', linewidth=2)
plt.plot(linear_interp, '--', label='Linear Interpolation', alpha=0.7)
plt.plot(smooth_savgol, '-', label='Smoothed (Savitzky-Golay)', linewidth=2, color='purple')
plt.title('Upsampling Yearly to Monthly: Savitzky-Golay Smoothing')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

🔍 Savitzky-Golay フィルターのポイント

パラメータ 意味
window_length スムージングに使うデータ点数(奇数)
polyorder 局所多項式の次数(通常は 2~3)
mode='interp' 端点を補間的に処理(端の歪み防止)

📊 出力の見た目

  • 元の年次データ:青丸
  • 線形補間:点線(角ばり)
  • Savitzky-Golay 補間:なめらかな紫線(カーブのある自然な動き)

必要であれば:

  • window_length の自動調整
  • 欠損が多いケースへの対応
  • 他のスムージング法(LOESS, kernel regression)

なども可能です!他にもやってみたい補間ありますか?

user:

seasonal_decompose や STL を使ってトレンドだけ抽出して平滑化する方法も書いてください。

assistant:

もちろんです!ここでは、年次データを月次にアップサンプリング・補間した後、seasonal_decomposeSTL(季節トレンド分解)を用いて トレンド成分だけを抽出して滑らかに補間する方法 を解説し、実装してみます。


🧠 方法の概要

  1. 年次データを月次にアップサンプリング
  2. 線形補間で補完
  3. seasonal_decompose または STL で分解
  4. トレンド成分を取り出して平滑な補間として利用
  5. 可視化

✅ 実装(seasonal_decompose & STL

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose, STL

# 1. 年次データ
years = pd.date_range('2015-01-01', periods=6, freq='AS')
values = pd.Series([100, 130, 120, 150, 140, 160], index=years)

# 2. 月次にアップサンプリング
monthly = values.resample('MS').asfreq()

# 3. 線形補間
interp = monthly.interpolate(method='linear')

# 4-1. seasonal_decompose(multiplicativeでなくadditive)
# 季節周期は12ヶ月
decomp = seasonal_decompose(interp, model='additive', period=12, extrapolate_trend='freq')

# 4-2. STL(よりロバスト、外れ値に強い)
stl = STL(interp, period=12, robust=True)
res_stl = stl.fit()

# 5. 可視化
plt.figure(figsize=(14, 6))
plt.plot(values, 'o-', label='Yearly Data', linewidth=2)
plt.plot(interp, '--', label='Linear Interpolation', alpha=0.6)
plt.plot(decomp.trend, label='Trend (seasonal_decompose)', linewidth=2, color='green')
plt.plot(res_stl.trend, label='Trend (STL)', linewidth=2, color='orange')
plt.title('Trend Extraction after Linear Interpolation')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

🧩 結果の比較

成分 seasonal_decompose STL
手法 古典的分解 より柔軟・ロバスト
特徴 シンプル・速い 外れ値に強い、カスタマイズ性高い
出力 trend, seasonal, resid 同様に .trend, .seasonal, .resid

✅ トレンドだけを補間に使う理由

  • 季節性の変動を含まずに「長期トレンドを平滑な曲線で近似」できる
  • 季節変動が存在しない、または小さい場合に特に有効
  • 外れ値や短期変動を除去したいときに便利

🔄 応用

  • トレンドと季節性を足し戻して補完値を構成することも可能
  • resid(残差)が小さければ、モデルフィットが良いという判断もできる

もし、トレンド+季節性で補完値を再構成する完全な補間もやってみたい場合は、それも追ってご紹介できます!
やってみますか?

user:

他のスムージング法(LOESS, kernel regression)を使う方法も書いてください。

assistant:

いいですね!LOESS(ローカル回帰)カーネル回帰(Kernel Regression) は、非線形トレンドやゆるやかな変化をなめらかに捉えるスムージング法で、時系列補間や平滑化にもとても役立ちます。
以下ではそれぞれを Python で実装する方法をご紹介します。


📌 1. LOESS(Lowess)を使ったスムージング

statsmodels.nonparametric.smoothers_lowess.lowess を使用します。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.nonparametric.smoothers_lowess import lowess

# 年次データを用意
years = pd.date_range('2015-01-01', periods=6, freq='AS')
values = pd.Series([100, 130, 120, 150, 140, 160], index=years)

# 月次にアップサンプリングして線形補間
monthly = values.resample('MS').asfreq()
interp = monthly.interpolate(method='linear')

# LOWESS スムージング
# frac: スムージングの程度(小さいほど局所的に)
lowess_result = lowess(interp.values, np.arange(len(interp)), frac=0.2, return_sorted=False)
smooth_loess = pd.Series(lowess_result, index=interp.index)

# 可視化
plt.figure(figsize=(14, 6))
plt.plot(values, 'o-', label='Yearly Data', linewidth=2)
plt.plot(interp, '--', label='Linear Interpolation', alpha=0.6)
plt.plot(smooth_loess, label='Smoothed (LOESS)', color='teal', linewidth=2)
plt.title('LOESS Smoothing on Upsampled Time Series')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

📌 2. カーネル回帰(Nadaraya-Watson Kernel Regression)

sklearn.neighbors.KernelDensity は確率密度推定なので、回帰には KernelReg を使うのがよいです(statsmodels.nonparametric.kernel_regression.KernelReg)。

from statsmodels.nonparametric.kernel_regression import KernelReg

# x: 時間(インデックスを数値化)
x = np.arange(len(interp))
y = interp.values

# カーネル回帰(local linear, ガウシアンカーネル)
kr = KernelReg(endog=y, exog=x, var_type='c')  # 'c' = continuous variable
y_pred, _ = kr.fit(x)
smooth_kernel = pd.Series(y_pred, index=interp.index)

# 可視化
plt.figure(figsize=(14, 6))
plt.plot(values, 'o-', label='Yearly Data', linewidth=2)
plt.plot(interp, '--', label='Linear Interpolation', alpha=0.6)
plt.plot(smooth_kernel, label='Smoothed (Kernel Regression)', color='crimson', linewidth=2)
plt.title('Kernel Regression Smoothing on Upsampled Time Series')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

✅ まとめ比較表

手法 モジュール 特徴
LOESS (Lowess) statsmodels.nonparametric 局所線形回帰。非線形なトレンドに強い。計算は少し重い
Kernel Regression statsmodels.nonparametric なめらかな非線形モデル。カーネル幅の調整が重要

👀 補足

  • frac(LOESS)や bandwidth(KernelReg の内部で自動選択)の値を変えるとスムージングの度合いが変わります
  • 時系列が長くなると計算コストが上がるので注意
  • 欠損値がある場合は、dropna() を忘れずに!

他にも例えば:

  • spline(補間兼スムージング)
  • Gaussian Process Regression(回帰+不確実性の推定)

などもあります。興味ありますか?

user:

ありがとうございます。試したいことはほぼ試せた気がします。今日の議論をブログに残したいので、タイトル案をいくつか提案していただけますか?

assistant:

もちろんです!今日の内容はかなり幅広く、実践的かつ技術的にも深いので、読者にとっても価値のあるブログになりますね。以下に 用途別やトーン別 にタイトル案をいくつかご提案します。


🧑‍🔬 技術系に特化したタイトル案(専門家・実装者向け)

  1. 「時系列データ補間・スムージング大全:Pythonで徹底比較」
  2. 「線形補間からSTL・LOESS・カーネル回帰まで:時系列データのなめらか補完術」
  3. 「Pythonで学ぶ時系列補間と平滑化:Statsmodels・SciPy・Sklearn編」
  4. 「時系列データの補完と可視化:12手法を一気に試してみた」
  5. 「年次→月次のアップサンプリング:精度と滑らかさのバランスを探る」

📝 読みやすさ・入門者向けのタイトル案

  1. 「時系列データの“足りない”を埋める!補間とスムージングの方法まとめ」
  2. 「Pythonでできる!時系列データのなめらか補完入門」
  3. 「線形補間じゃ物足りない?時系列のスムージング技術まとめてみた」

🔬 モデル志向・理論寄りなタイトル案

  1. 「時系列補間の科学:線形、カルマン、STL、プロフェット、そしてカーネルへ」
  2. 「観測されなかった時を埋める:時系列補間とその数理的背景」

気に入ったものがあればそれを使えますし、「もう少しカジュアルに」や「応用例も含めたい」などあれば、方向性を絞ってもっとご提案できますよ!どれが一番しっくりきましたか?

user:

「線形補間じゃ物足りない?時系列のスムージング技術まとめてみた」で行きたいと思います。最後に、今回のまとめを詳しく書いてください。

assistant:

了解しました!ブログの内容のまとめを以下に記載いたします。ブログとしても読者にとって実践的でわかりやすいものに仕上がるよう心がけています。


線形補間じゃ物足りない?時系列のスムージング技術まとめてみた

はじめに

時系列データは、予測、分析、意思決定に欠かせない重要な情報源です。しかし、現実のデータはしばしば欠損値やノイズを含んでおり、そのままでは精度の高い分析ができません。そこで登場するのが「補間」と「スムージング」の技術です。

特に「線形補間」は簡単で一般的ですが、データの変動が激しい場合や非線形の傾向が強い場合には、補間結果がカクカクしたり不自然な形になることがあります。そこで、もっと精緻で自然な補間を行うための技術をいくつか試してみました。今回は、その中でも有効な手法を紹介し、実際にPythonでどのように実装できるかを見ていきます。


1. 線形補間:基本の手法

線形補間は、2つのデータポイントを結ぶ直線を使って欠損値を補う方法です。シンプルで高速ですが、データが急激に変動する場合や、非線形の傾向を持つ場合には、自然でない補間結果となることがあります。

# 線形補間
linear_interp = monthly.interpolate(method='linear')

この方法は、欠損値が少なく、比較的平滑なデータで有効ですが、時系列データに不規則な変動が多い場合は限界があります。


2. LOESS(Lowess)によるスムージング

LOESS(Lowess)は「ローカル加重回帰」を用いたスムージング手法で、局所的にデータを平滑化し、非線形トレンドを捉えることができます。この方法は、急激な変動を平滑化し、データの本来の傾向をうまく反映できます。

from statsmodels.nonparametric.smoothers_lowess import lowess
lowess_result = lowess(interp.values, np.arange(len(interp)), frac=0.2, return_sorted=False)

frac はスムージングの強さを調整するパラメータで、データの特性に合わせて最適な値を選ぶ必要があります。


3. カーネル回帰(Kernel Regression)

カーネル回帰は、非線形関係をモデル化するための手法で、データ点周辺の局所的な回帰を行い、滑らかな補間を可能にします。特にノイズが多い場合やデータの変動が複雑な場合に有効です。

from statsmodels.nonparametric.kernel_regression import KernelReg
kr = KernelReg(endog=y, exog=x, var_type='c')
y_pred, _ = kr.fit(x)

カーネル回帰では、適切なカーネル幅(bandwidth)を設定することが重要です。


4. STL(Seasonal and Trend Decomposition using Loess)によるトレンド抽出

STLは、時系列データを季節成分、トレンド成分、残差に分解する強力な手法です。トレンド成分のみを抽出して補間することで、データの「大まかな傾向」を捉え、細かい変動を除外できます。

from statsmodels.tsa.seasonal import STL
stl = STL(interp, period=12, robust=True)
res_stl = stl.fit()

STLを使用することで、季節性やトレンドを分けて扱うことができ、補間後の結果が滑らかで自然なものになります。


5. Savitzky-Golay フィルターによる平滑化

Savitzky-Golayフィルターは、データを多項式で近似し、スムーズに平滑化する方法です。特に短期的なノイズを除去したい場合に有効です。

from scipy.signal import savgol_filter
smooth_savgol = savgol_filter(linear_interp.values, window_length=11, polyorder=2)

この手法は、データが非常にノイジーな場合や急激な変動が含まれる時に、よりなめらかな曲線を得るために使用します。


6. Prophetによる補間(柔軟な非線形モデル)

FacebookのProphetは、時系列予測専用のモデルですが、補間にも使用できます。季節性や休日の影響を考慮しながら、データのトレンドや季節変動をモデル化できます。

from fbprophet import Prophet
# Prophetを使ったモデル構築と予測

特に、長期的なトレンドや季節的な影響を考慮した補間に向いています。


まとめ

今回紹介した手法は、基本的な線形補間から始まり、LOESS、カーネル回帰、STL、Savitzky-Golayフィルター、さらにはProphetまで多岐にわたります。これらの方法は、データの特性や目的に応じて使い分けることが重要です。たとえば、急激な変動を滑らかにしたい場合にはLOESSやカーネル回帰、季節性を捉えたい場合にはSTLが有効です。

どの手法もPythonで簡単に実装でき、目的に応じたスムージングを行うことができます。データ分析や予測の精度を向上させるために、これらのスムージング技術を上手に活用してみましょう!


いかがでしょうか?読者にとってわかりやすく実践的な内容になっていると思います。必要に応じて細かい部分を調整したり、さらなる補足を加えることもできます。

user:

ありがとうございます。また今度よろしくお願いします!

assistant:

こちらこそ、楽しいディスカッションをありがとうございました!またいつでも質問や相談があれば、お気軽にお声かけくださいね。次回もお待ちしています!😊

良いブログ作成を!またね!👋

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?