{ "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "23b89db2", "metadata": {}, "source": [ "# Smoothing" ] }, { "attachments": {}, "cell_type": "markdown", "id": "d11353c9", "metadata": {}, "source": [ "這邊我們先給各位看一下使用numpy手刻完成一些最簡單的forcasting\n", "\n", "numpy手刻只是為了讓同學了解這些算法在使用python實現的方式,後面我們會附上一些簡單使用的套件,以後在使用時直接call套件的class或者funciton就可以簡單實現演算法" ] }, { "attachments": {}, "cell_type": "markdown", "id": "773dfb78", "metadata": {}, "source": [ "課程包含以下內容:\n", "- Moving Average\n", "- Simple Exponential Smoothing\n", "- Finite Difference" ] }, { "attachments": {}, "cell_type": "markdown", "id": "e3036753", "metadata": {}, "source": [ "#### **開始前請先安裝或import基本套件**\n", "#### **若使用Jupyter Notebook開啟請轉成tree view方便顯示plotly出來的圖**" ] }, { "cell_type": "code", "execution_count": null, "id": "a6d90e44", "metadata": { "scrolled": true }, "outputs": [], "source": [ "! pip install --user plotly" ] }, { "cell_type": "code", "execution_count": null, "id": "520adcab", "metadata": {}, "outputs": [], "source": [ "%matplotlib notebook\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "from plotly import express as px" ] }, { "attachments": {}, "cell_type": "markdown", "id": "069f8b52", "metadata": {}, "source": [ "**另外我們也先準備一個畫圖的function,我們不會放重點在這邊但後面會用它來看一些time series處理的過程**" ] }, { "cell_type": "code", "execution_count": null, "id": "84e1a5a6", "metadata": {}, "outputs": [], "source": [ "def plot_series(time, series, start=0, end=None, labels=None, title=None):\n", " # Visualizes time series data\n", " # Args:\n", " # time (array of int) - 時間點, 長度為T\n", " # series (list of array of int) - 時間點對應的資料列表,列表內時間序列數量為D,\n", " # 每筆資料長度為T,若非為列表則轉為列表\n", " # start (int) - 開始的資料序(第幾筆)\n", " # end (int) - 結束繪製的資料序(第幾筆)\n", " # labels (list of strings)- 對於多時間序列或多維度的標註\n", " # title (string)- 圖片標題\n", "\n", " # 若資料只有一筆,則轉為list\n", " if type(series) != list:\n", " series = [series]\n", "\n", " if not end:\n", " end = len(series[0])\n", "\n", " if labels:\n", " # 設立dictionary, 讓plotly畫訊號線時可以標註label\n", " dictionary = {\"time\": time}\n", " for idx, l in enumerate(labels):\n", " # 截斷資料,保留想看的部分,並分段紀錄於dictionary中\n", " dictionary.update({l: series[idx][start:end]})\n", " # 畫訊號線\n", " fig = px.line(dictionary,\n", " x=\"time\",\n", " y=list(dictionary.keys())[1:],\n", " width=1000,\n", " height=400,\n", " title=title)\n", " else:\n", " # 畫訊號線\n", " fig = px.line(x=time, y=series, width=1000, height=400, title=title)\n", " fig.show()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "c55f39d1", "metadata": {}, "source": [ "這邊也附上我們的toy data產生器" ] }, { "cell_type": "code", "execution_count": null, "id": "87447c42", "metadata": {}, "outputs": [], "source": [ "def trend(time, slope=0):\n", " # 產生合成水平直線資料,其長度與時間等長,直線趨勢與設定slope相同\n", " # Args:\n", " # time (array of int) - 時間點, 長度為T\n", " # slope (float) - 設定資料的傾斜程度與正負\n", " # Returns:\n", " # series (array of float) - 產出slope 與設定相同的一條線\n", "\n", " series = slope * time\n", "\n", " return series\n", "\n", "\n", "def seasonal_pattern(season_time, pattern_type='triangle'):\n", " # 產生某個特定pattern,\n", " # Args:\n", " # season_time (array of float) - 周期內的時間點, 長度為T\n", " # pattern_type (str) - 這邊提供triangle與cosine\n", " # Returns:\n", " # data_pattern (array of float) - 根據自訂函式產出特定的pattern\n", "\n", " # 用特定function生成pattern\n", " if pattern_type == 'triangle':\n", " data_pattern = np.where(season_time < 0.5,\n", " season_time*2,\n", " 2-season_time*2)\n", " if pattern_type == 'cosine':\n", " data_pattern = np.cos(season_time*np.pi*2)\n", "\n", " return data_pattern\n", "\n", "\n", "def seasonality(time, period, amplitude=1, phase=30, pattern_type='triangle'):\n", " # Repeats the same pattern at each period\n", " # Args:\n", " # time (array of int) - 時間點, 長度為T\n", " # period (int) - 週期長度,必小於T\n", " # amplitude (float) - 序列幅度大小\n", " # phase (int) - 相位,為遞移量,正的向左(提前)、負的向右(延後)\n", " # pattern_type (str) - 這邊提供triangle與cosine\n", " # Returns:\n", " # data_pattern (array of float) - 有指定周期、振幅、相位、pattern後的time series\n", "\n", " # 將時間依週期重置為0\n", " season_time = ((time + phase) % period) / period\n", "\n", " # 產生週期性訊號並乘上幅度\n", " data_pattern = amplitude * seasonal_pattern(season_time, pattern_type)\n", "\n", " return data_pattern\n", "\n", "\n", "def noise(time, noise_level=1, seed=None):\n", " # 合成雜訊,這邊用高斯雜訊,機率密度為常態分布\n", " # Args:\n", " # time (array of int) - 時間點, 長度為T\n", " # noise_level (float) - 雜訊大小\n", " # seed (int) - 同樣的seed可以重現同樣的雜訊\n", " # Returns:\n", " # noise (array of float) - 雜訊時間序列\n", "\n", " # 做一個基於某個seed的雜訊生成器\n", " rnd = np.random.RandomState(seed)\n", "\n", " # 生與time同長度的雜訊,並且乘上雜訊大小 (不乘的話,標準差是1)\n", " noise = rnd.randn(len(time)) * noise_level\n", "\n", " return noise\n", "\n", "\n", "def toy_generation(time=np.arange(4 * 365),\n", " bias=500.,\n", " slope=0.1,\n", " period=180,\n", " amplitude=40.,\n", " phase=30,\n", " pattern_type='triangle',\n", " noise_level=5.,\n", " seed=2022):\n", " signal_series = bias\\\n", " + trend(time, slope)\\\n", " + seasonality(time,\n", " period,\n", " amplitude,\n", " phase,\n", " pattern_type)\n", " noise_series = noise(time, noise_level, seed)\n", "\n", " series = signal_series+noise_series\n", " return series" ] }, { "attachments": {}, "cell_type": "markdown", "id": "ac1cd5d5", "metadata": {}, "source": [ "我們附上最naive的k-step ahead prediction" ] }, { "cell_type": "code", "execution_count": null, "id": "37ebddc7", "metadata": {}, "outputs": [], "source": [ "def k_step_ahead(data, k):\n", " # 產生k-step ahead預測\n", " # Args:\n", " # data (array of float) - 輸入資料\n", " # Returns:\n", " # forcast (array of float) - k-step ahead預測結果\n", "\n", " forcast = data[:-k]\n", " return forcast" ] }, { "attachments": {}, "cell_type": "markdown", "id": "24075228", "metadata": {}, "source": [ "也附上評估的function" ] }, { "cell_type": "code", "execution_count": null, "id": "373172ec", "metadata": {}, "outputs": [], "source": [ "def MAE(pred, gt):\n", " # 計算Mean Absolute Error\n", " # Args:\n", " # pred (array of float) - 預測資料\n", " # gt (array of float) - 答案資料\n", " # Returns:\n", " # 計算結果 (float)\n", " return abs(pred-gt).mean()\n", "\n", "\n", "def MSE(pred, gt):\n", " # 計算Mean Square Error\n", " # Args:\n", " # pred (array of float) - 預測資料\n", " # gt (array of float) - 答案資料\n", " # Returns:\n", " # 計算結果 (float)\n", " return pow(pred-gt, 2).mean()\n", "\n", "\n", "def R2(pred, gt):\n", " # 計算R square score\n", " # Args:\n", " # pred (array of float) - 預測資料\n", " # gt (array of float) - 答案資料\n", " # Returns:\n", " # 計算結果 (float)\n", " return 1-pow(pred-gt, 2).sum()/pow(gt-gt.mean(), 2).sum()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "2ae1e65e", "metadata": {}, "source": [ "另外我們記得生成資料" ] }, { "cell_type": "code", "execution_count": null, "id": "972525a8", "metadata": {}, "outputs": [], "source": [ "# 先合成資料\n", "time = np.arange(4 * 365) # 定義時間點\n", "series_sample = toy_generation(time) # 這就是我們合成出來的資料\n", "\n", "\n", "# 最簡單直接取前後,並且時間點也記得要切,我們直接立個function\n", "def split(x, train_size):\n", " return x[..., :train_size], x[..., train_size:]\n", "\n", "\n", "time_train, time_test = split(time, 365*3)\n", "series_train, series_test = split(series_sample, 365*3)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "66ac014b", "metadata": {}, "source": [ "## Moving Average" ] }, { "attachments": {}, "cell_type": "markdown", "id": "2a7686ee", "metadata": {}, "source": [ "Moving average 把前K個時間點的資料做平均,使用這平均來預測下筆資料。\n", "\n", "\n", "實現方式有很多種,包含對每個時間點慢慢使用平均、或者使用捲積運算等等。\n", "\n", "因為是對未來的預測,所以會希望前K天[t-K,...,t-2,t-1]資料預測該時間點(t)資料" ] }, { "cell_type": "code", "execution_count": null, "id": "f11ddd1b", "metadata": { "scrolled": true }, "outputs": [], "source": [ "# 用捲積做看看5天平均的moving average\n", "K = 5\n", "kernel = np.ones(K)/K # 對K個1做捲積在除以K就是做移動的平均\n", "# 最開始會取最前K筆做第一次平均,最後一次算會包含最後一筆,所以要去掉\n", "forcast = np.convolve(series_train, kernel, mode='valid')[:-1]\n", "ground_truth_for_view = series_train[K:] # 去掉前K日資料\n", "time_for_view = time_train[K:]\n", "\n", "plot_series(time_for_view,\n", " [forcast, ground_truth_for_view],\n", " labels=['prediction', 'ground truth'])" ] }, { "attachments": {}, "cell_type": "markdown", "id": "31a1862d", "metadata": {}, "source": [ "可以把它包起來" ] }, { "cell_type": "code", "execution_count": null, "id": "16355465", "metadata": {}, "outputs": [], "source": [ "def moving_average(data, K):\n", " # 產生前k個資料的moving average預測\n", " # Args:\n", " # data (array of float) - 輸入資料\n", " # K (float) - 每次要平均的資料筆數\n", " # Returns:\n", " # forcast (array of float) - moving average預測結果\n", "\n", " forcast = np.convolve(data, np.ones(K)/K, mode='valid')[:-1]\n", " return forcast" ] }, { "attachments": {}, "cell_type": "markdown", "id": "b85eb2ee", "metadata": {}, "source": [ "## Simple Exponential Smoothing" ] }, { "attachments": {}, "cell_type": "markdown", "id": "e29bd4e5", "metadata": {}, "source": [ "若考慮到更遠古的資料對現在的預測來講,重要性應該要下降,可以使用Simple Exponential Smoothing。\n", "\n", "$$F_{t}=\\alpha y_{t-1}+(1-\\alpha) F_{t-1}$$\n", "\n", "\n", "它會將前一時間點[t-1]的預測與前個時間點的資料做加權和,而前一個時間點的預測亦是從前前時間點[t-2]的資料來的,當權重$1-\\alpha$介於$[0,1)$時將會使遠古的資料影響力隨著時間逐漸趨於0。\n", "\n", "$$F_{t}=\\alpha y_{t-1}+\\alpha(1-\\alpha) y_{t-2}+\\alpha(1-\\alpha)^2 y_{t-3}+...+\\alpha(1-\\alpha)^{t-1} y_{0}$$\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "9c372487", "metadata": {}, "outputs": [], "source": [ "# 我們試著用for迴圈推每次的結果\n", "alpha = 0.9\n", "forcast = []\n", "for t in range(1, len(series_train)):\n", " if forcast:\n", " forcast.append(alpha*series_train[t-1]+alpha*(1-alpha)*forcast[-1])\n", " else:\n", " forcast = [alpha*series_train[t-1]]\n", "forcast = np.array(forcast)\n", "\n", "ground_truth_for_view = series_train[1:] # 去掉前1日資料\n", "time_for_view = time_train[1:]\n", "\n", "plot_series(time_for_view,\n", " [ground_truth_for_view, forcast],\n", " labels=['ground truth', 'ses'])" ] }, { "attachments": {}, "cell_type": "markdown", "id": "98ce65b8", "metadata": {}, "source": [ "試著show 一下weight項,我們看\n", "$$[\\alpha,\\alpha(1-\\alpha),\\alpha(1-\\alpha)^2,\\alpha(1-\\alpha)^3,...]$$\n", "$$=\\alpha*[(1-\\alpha)^0,(1-\\alpha)^1,(1-\\alpha)^2,...]$$" ] }, { "cell_type": "code", "execution_count": null, "id": "59cfce20", "metadata": {}, "outputs": [], "source": [ "alpha = 0.9\n", "# get [0,1,2,3,...]\n", "exp = np.arange(0, len(series_train))\n", "\n", "# get [(1-alpha),(1-alpha),(1-alpha),...]\n", "a = np.repeat(1-alpha, len(series_train))\n", "\n", "# get power of (1-alpha)\n", "weight = np.power(a, exp)*alpha" ] }, { "cell_type": "code", "execution_count": null, "id": "a8616528", "metadata": {}, "outputs": [], "source": [ "# 可調整alpha嘗試看看,大概前幾個weight就太小了,後面基本可忽略\n", "weight[:10]" ] }, { "cell_type": "code", "execution_count": null, "id": "913fcb80", "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(10, 2))\n", "plt.bar(range(1, 11), weight[:10], tick_label=[str(n) for n in range(1, 11)])" ] }, { "cell_type": "code", "execution_count": null, "id": "d7a771c0", "metadata": {}, "outputs": [], "source": [ "def ses(data, k, alpha=0.95):\n", " # 產生關於K個時間點後的Simple Exponential Smoothing預測\n", " # Args:\n", " # data (array of float) - 輸入資料\n", " # alpha (float) - 衰減係數,決定過往資料比重\n", " # k (float) - 要往後預測的資料筆數\n", " # Returns:\n", " # forcast (array of float) - Simple Exponential Smoothing預測結果\n", "\n", " forcast = []\n", " for t in range(1, len(data)):\n", " if forcast:\n", " forcast.append(alpha*data[t-1]+alpha*(1-alpha)*forcast[-1])\n", " else:\n", " forcast = [data[t-1]]\n", " return np.array(forcast)[:-K+1]" ] }, { "attachments": {}, "cell_type": "markdown", "id": "37c79f01", "metadata": {}, "source": [ "## Comparison" ] }, { "attachments": {}, "cell_type": "markdown", "id": "79d02d44", "metadata": {}, "source": [ "把前述幾個演算法互相比較一下" ] }, { "cell_type": "code", "execution_count": null, "id": "9b0cafd0", "metadata": {}, "outputs": [], "source": [ "# 我們把它跟k_step比較一下結果\n", "K = 5\n", "forcast_d = {\n", " 'ground_truth': series_train[K:], # 去掉前K日資料\n", " 'k_step': k_step_ahead(series_train, K),\n", " 'ses': ses(series_train, K),\n", " 'ma': moving_average(series_train, K)\n", "}\n", "\n", "time_for_view = time_train[K:]\n", "\n", "plot_series(time_for_view,\n", " list(forcast_d.values()),\n", " labels=list(forcast_d.keys()))" ] }, { "cell_type": "code", "execution_count": null, "id": "c2cb8b62", "metadata": {}, "outputs": [], "source": [ "for method, pred in [*forcast_d.items()][1:]:\n", " print(f'''{method}:\n", " -MAE:{MAE(pred[5:], forcast_d['ground_truth'][5:])}\n", " -MSE:{MSE(pred[5:], forcast_d['ground_truth'][5:])}\n", " -R2:{R2(pred[5:], forcast_d['ground_truth'][5:])}''')" ] }, { "attachments": {}, "cell_type": "markdown", "id": "3367ba4e", "metadata": {}, "source": [ "目前的資料中因為沒有除卻trend與bias的關係,k-step與ses的效果沒辦法發揮,後面做到linear regression後可再試試。" ] }, { "attachments": {}, "cell_type": "markdown", "id": "7ffb3348", "metadata": {}, "source": [ "### 模擬de-trend後的結果" ] }, { "cell_type": "code", "execution_count": null, "id": "9f08ab48", "metadata": { "scrolled": true }, "outputs": [], "source": [ "mytrend = trend(time_train, 0.1)+500.\n", "series_train2 = series_train-mytrend\n", "\n", "K = 180\n", "\n", "forcast_d = {\n", " 'ground_truth': series_train2[K:], # 去掉前K日資料\n", " 'k_step': k_step_ahead(series_train2, K),\n", " 'ses': ses(series_train2, K, alpha=0.7),\n", " 'ma': moving_average(series_train2, K)\n", "}\n", "\n", "time_for_view = time_train[K:]\n", "\n", "plot_series(time_for_view,\n", " list(forcast_d.values()),\n", " labels=list(forcast_d.keys()))" ] }, { "cell_type": "code", "execution_count": null, "id": "00edbe1c", "metadata": {}, "outputs": [], "source": [ "for method, pred in [*forcast_d.items()][1:]:\n", " print(f'''{method}:\n", " -MAE:{MAE(pred[5:], forcast_d['ground_truth'][5:])}\n", " -MSE:{MSE(pred[5:], forcast_d['ground_truth'][5:])}\n", " -R2:{R2(pred[5:], forcast_d['ground_truth'][5:])}''')" ] }, { "attachments": {}, "cell_type": "markdown", "id": "6ce2ad36", "metadata": {}, "source": [ "可以看出來,在去掉trend與bias後,使用k-step與ses推估已知周期(180天)後的序列,效果會比做180天的moving average好很多。" ] }, { "attachments": {}, "cell_type": "markdown", "id": "1bcc1c15", "metadata": {}, "source": [ "## Finite Difference" ] }, { "attachments": {}, "cell_type": "markdown", "id": "85dcde82", "metadata": {}, "source": [ "當我們預期時間序列在短時間內有比較大的變動時,我們常常不會直接predict序列本身而是序列的有限差分(finite difference)。\n", "$$d_0=not exist$$\n", "$$d_t=y_t-y_{t-1}$$\n", "\n", "這樣會使得序列中最高頻率的特性被抓出來,低頻的trend與bias也可以透過如此方法被去掉,再預測時會比較方便。\n", "\n", "不過有些頻率太低的訊號特性也會同時被削減。\n", "\n", "而且這樣的訊號不是元訊號,預測完還須還原就是了。" ] }, { "cell_type": "code", "execution_count": null, "id": "9d5bd448", "metadata": { "scrolled": true }, "outputs": [], "source": [ "def dif(x):\n", " return x[1:]-x[:-1]\n", "plot_series(time_train, series_train, title='Original')\n", "diff = dif(series_train)\n", "plot_series(time_train[1:], diff, title='Difference')" ] }, { "attachments": {}, "cell_type": "markdown", "id": "abd9fff2", "metadata": {}, "source": [ "### Recover Finite Difference\n", "既然是差分,只要給予原本訊號中最前面的資料,再一一做summary就可以復原。\n", "$$r_0=y_0$$\n", "$$r_t=y_{t-1}+d_{t}$$" ] }, { "cell_type": "code", "execution_count": null, "id": "77b6faa8", "metadata": {}, "outputs": [], "source": [ "plot_series(time_train,\n", " [series_train, np.cumsum([series_train[0], *diff])],\n", " labels=['ground truth', 'recovered'])" ] }, { "cell_type": "code", "execution_count": null, "id": "4c183651", "metadata": {}, "outputs": [], "source": [ "diff = dif(series_train)\n", "K = 5\n", "\n", "forcast_d = {\n", " 'ground_truth': diff[K:], # 去掉前K日資料\n", " 'k_step': k_step_ahead(diff, K),\n", " 'ses': ses(diff, K, alpha=0.7),\n", " 'ma': moving_average(diff, K)\n", "}\n", "\n", "time_for_view = time_train[K+1:]\n", "\n", "plot_series(time_for_view,\n", " list(forcast_d.values()),\n", " labels=list(forcast_d.keys()))" ] }, { "attachments": {}, "cell_type": "markdown", "id": "8c465b9e", "metadata": {}, "source": [ "例如目前我們的序列是180天的周期,形成差分序列時已經把大部分的內容削去幾乎只剩noise\n", "\n", "所以這種大時間尺度序列的其實用difference可能model不出什麼東西" ] }, { "cell_type": "code", "execution_count": null, "id": "dff8a7cf", "metadata": {}, "outputs": [], "source": [ "for method, pred in [*forcast_d.items()][1:]:\n", " print(f'''{method}:\n", " -MAE:{MAE(pred[5:], forcast_d['ground_truth'][5:])}\n", " -MSE:{MSE(pred[5:], forcast_d['ground_truth'][5:])}\n", " -R2:{R2(pred[5:], forcast_d['ground_truth'][5:])}''')" ] }, { "attachments": {}, "cell_type": "markdown", "id": "516dc681", "metadata": {}, "source": [ "## Call Function from Module" ] }, { "attachments": {}, "cell_type": "markdown", "id": "af2e6f76", "metadata": {}, "source": [ "### Moving average" ] }, { "attachments": {}, "cell_type": "markdown", "id": "135930f3", "metadata": {}, "source": [ "Moving Average因為很簡單,所以沒有在大的module裡面有工具。\n", "\n", "但在Pandas有```Series.rolling(window).mean()```可以用,其中window就是window size,一次要平均的資料比數\n", "\n", "(不過先存成Series會比較耗資源,可能自己用前面方法寫結果還跑得比較快)" ] }, { "cell_type": "code", "execution_count": null, "id": "d7253cc7", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "forcast = pd.Series(series_train).rolling(5).mean()\n", "\n", "plot_series(time_train,\n", " [series_train, forcast],\n", " labels=['ground truth', f'ma,window=5'])" ] }, { "attachments": {}, "cell_type": "markdown", "id": "a0e0898c", "metadata": {}, "source": [ "### SES" ] }, { "attachments": {}, "cell_type": "markdown", "id": "b3d137ee", "metadata": {}, "source": [ "SES較複雜一點,在statsmodels有工具,並且具有training alpha的功能以及做完detrend" ] }, { "cell_type": "code", "execution_count": null, "id": "1fe6b7ce", "metadata": {}, "outputs": [], "source": [ "from statsmodels.tsa.api import SimpleExpSmoothing as SES\n", "# smoothing_level: alpha\n", "# model = SES(series_train).fit(smoothing_level=0.2, optimized=False)\n", "model = SES(series_train).fit()\n", "# 後面fit那邊不填入任何內容可以自動train alpha\n", "\n", "forcast = model.fittedvalues\n", "\n", "plot_series(time_train,\n", " [series_train, forcast],\n", " labels=['ground truth',\n", " f'ses, alpha={model.params[\"smoothing_level\"]}'])\n", "\n", "# 其中 detrend加recover它都幫我們做好了,所以不受trend影響" ] }, { "cell_type": "code", "execution_count": null, "id": "3287a9f4", "metadata": {}, "outputs": [], "source": [ "# 預測在測試集上\n", "model = SES(series_train).fit()\n", "\n", "forcast = SES(series_test).fit(\n", " smoothing_level=model.params[\"smoothing_level\"],\n", " optimized=False).fittedvalues\n", "\n", "plot_series(time_test,\n", " [series_test, forcast],\n", " labels=['ground truth',\n", " f'ses,alpha={model.params[\"smoothing_level\"]}'])" ] }, { "attachments": {}, "cell_type": "markdown", "id": "1022067b", "metadata": {}, "source": [ "### Diff\n", "Finite Difference也很簡單,不過可以使用```np.diff(series,n)```來做。\n", "\n", "series就是目標序列,n代表做幾次差分,可以做超過一階的差分" ] }, { "cell_type": "code", "execution_count": null, "id": "2e878ca4", "metadata": {}, "outputs": [], "source": [ "print(np.diff([1, 2, 3, 4, 5], n=1))\n", "print(np.diff([1, 2, 3, 4, 5], n=2))" ] }, { "cell_type": "code", "execution_count": null, "id": "ab135327", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.12" } }, "nbformat": 4, "nbformat_minor": 5 }