2018年5月29日 星期二

Coursera - Applied Data Science with Python 學習筆記 02 - matplotlib

香港2018年對比以往10年的極端氣溫 - 現在 5月尾 (至27/5) 已經連續出現破記錄的高溫

第二課:<Applied Plotting, Charting & Data Representation in Python> 學的是繪圖,倚重 Jupyter Notebook 中的 %matplotlib 功能來直接顯示繪圖的輸出。以上的圖就是其中一功課改用香港數據表示出來,詳細coding看本文的最後部份。 Jupyter Notebook 以前稱為 IPython notebook,從前安裝 Anaconda 應該是預設了Jupyter,只是一直未有使用。今次先檢查兩個2.7和3.6的python環境中的package,未有Jupyter的話就重新安裝/更新。

# 當前環境的套件
conda list
# 更新 pip
pip install --upgrade pip

# 安裝 jupyter
pip install jupyter

之後當執行jupyter,電腦會打開一個瀏覽器視窗,預設會連到localhost的 port:8888。新增或打開課程的 .ipynb 檔,當中的內容以一個個的「Cell」顯示。按 [Shift+Enter] 執行這個 Cell ,某個 Cell 的 in[?] 的結果會顯示在相應的 out[?] 之中,這樣可以選擇文件中的某部分來執行、逐步去測試。 其他功能可以在文件上方的選項,經常用到 Kernal 中的 Restart, Restart+Run all。在用完某 .ipynb 後可以把它 Shutdown;要離開 Jupyter 就可以在 Terminal 中按 [Ctrl+C] x2

# Use Jupyter
python -m jupyter notebook

用瀏覽器的 localhost:8888/tree/ 作為開頭的路徑,前往電腦中的檔案夾

打開 Jupyter Notebook 的 .ipynb 檔


%matplotlib inline 或者 %matplotlib notebook 是一個 Jupyter "Magic Commands",可以讓繪圖直接輸出在Notebook的環境中。magic command是notebook中的一系列特別指令,讓使用者可以控制notebook的行為和系統特徵。部分的魔法指令如下:

%magic   # magic指令的介紹
%lsmagic   # 可以調用的magic指令列表
%pwd   # 顯示目前目錄
%whos   # 列出環境中的變數資訊
%matplotlib   # 設立 matplotlib 在方便繪圖的顯示,預設 GUI backend 為 TkAgg -  %matplotlib inline,  %matplotlib notebook
%autosave 0   # 關閉autosave,預設自動存檔的間格為12秒 -  %autosave 120
%timeit   # 計算 Inline 的程式碼執行的時間 -  %timeit -n<N> -r<R>
%%timeit   # 計算 Cell 的程式碼執行的時間
%%html   # Render as HTML
%%javascript   # Render as Javascript
%%latex   # Render as LATEX

用 %matplotlib 在Notebook中繪圖的例子

1. 總結一些有用的每周例子所學

# 第一個 Scatter Plot
plt.scatter(x, y, s=10, c=colors)   # 用 x, y 繪圖,資料點的大小為100, 顏色為colors。
# 加入各種標示的設定
plt.xlabel('xlabel')
plt.ylabel('ylabel')
plt.title('Title')
plt.legend(loc=4, frameon=False, title='Legend')
# 加入各種標示的設定 by plt.gca()
ax = plt.gca()
ax.get_children()
ax.set_xlabel('xlabel')
ax.set_ylabel('ylabel')
ax.set_title("Title with Exponential ($x^2$) formula")
for item in ax.xaxis.get_ticklabels():
    item.set_rotation(45)


# 折線圖 - Line Charts
linear_data = np.array([1,2,3,4,5,6,7,8])
exponential_data = linear_data**2
plt.plot(linear_data, '-o', exponential_data, '-o')
# 為兩數列間的範圍著色
plt.gca().fill_between(range(len(linear_data)),
                       linear_data, exponential_data,
                       facecolor='blue',
                       alpha=0.25)
# 以日期為 index ,使用 dataframe 的 Line Plot 


# 柱狀圖 - Bar Charts
xvals = range(len(linear_data))
plt.bar(xvals, linear_data, width = 0.3)
# 加入第二組柱
new_xvals=[]
for item in xvals:
    new_xvals.append(item+0.3)
plt.bar(new_xvals, exponential_data, width = 0.3 ,color='red')
# 在柱身的高度上加入error bar
from random import randint
linear_err = [randint(0,2) for x in range(len(linear_data))]
plt.bar(xvals, linear_data, width = 0.3, yerr=linear_err)

# 在主圖figure中分配子圖subplot
plt.subplot(1, 2, 1)   # 1row, 2cols 的間隔中,第 1 張圖
plt.plot( np.array([1,2,3,4,5,6,7,8]) , '-o')
plt.subplot(1, 2, 2)   # 1row, 2cols 的間隔中,第 2 張圖
plt.plot( np.array([1,2,3,4,5,6,7,8])**2 , '-x')
# 不同sample size 下的normal distribution 桂狀圖
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, sharex=True)
axs = [ax1,ax2,ax3,ax4]
for n in range(0,len(axs)):
    sample = np.random.normal(loc=0.0, scale=1.0, size=10**(n+1))
    axs[n].hist(sample, bins=100)
    axs[n].set_title('n={}'.format(10**(n+1)))

iris = pd.read_csv('iris.csv')
pd.tools.plotting.scatter_matrix(iris)
import seaborn as sns
np.random.seed(1234)
v1 = pd.Series(np.random.normal(0,10,1000), name='v1')
v2 = pd.Series(2*v1 + np.random.normal(60,15,1000), name='v2')
grid = sns.jointplot(v1, v2, alpha=0.4);
grid.ax_joint.set_aspect('equal')

以上都是一些靜態圖片,如果用 %matplotlib notebook 代替 %matplotlib inline 的話就可以顯示 notebook 的動態和互動的繪圖功能。

# 動畫顯示
import matplotlib.animation as animation  # 加入動態更新作圖
n = 100
x = np.random.randn(n)   # 準備一組 Normal Distribution的 100 個數據
bins = np.arange(-4, 4, 0.5)   # 準備繪製Histogram時所用的間隔

def update(curr):   # 定義如何更新繪圖的 update(curr)
    if curr == n:
        a.event_source.stop()    # 當 curr =100 時停止更新
    plt.cla()    # 先清除舊圖

    plt.hist(x[:curr], bins=bins)    # 繪製新圖
    plt.axis([-4,4,0,30])
    plt.gca().set_title('Sampling the Normal Distribution')
    plt.gca().set_ylabel('Frequency')
    plt.gca().set_xlabel('Value')
    plt.annotate('n = {}'.format(curr), [3,27])

fig = plt.figure()
a = animation.FuncAnimation(fig, update, interval=100)   # 綁定'update'事件到 Figure:fig 的動畫功能。

n 會從 1 至 100 地更新


# 互動顯示
from random import shuffle
origins = ['Islands', 'Kwai Tsing', 'North', 'Sai Kung', 'Sha Tin', 'Tai Po', 'Tsuen Wan', 'Tuen Mun', 'Yuen Long', 'Kowloon City, 'Kwun Tong', 'Sham Shui Po', 'Wong Tai Sin', 'Yau Tsim Mong', 'Central & Western', 'Eastern', 'Southern', 'Wan Chai']
shuffle(origins)
df = pd.DataFrame({'variable1': np.random.rand(18),
                   'variable2': np.random.rand(18),
                   'origin': origins})

plt.figure()
plt.scatter(df['variable1'], df['variable2'], picker=5)   # 繪圖,設定滑鼠在 5px 的範圍內選取資料
plt.gca().set_ylabel('Variable2')
plt.gca().set_xlabel('Variable1')

def onpick(event):  # 定義一個事件觸發後的行為 onpick(event) -顯示選取的數據
    origin = df.iloc[event.ind[0]]['origin']
    plt.gca().set_title('Selected item came from {}'.format(origin))
plt.gcf().canvas.mpl_connect('pick_event', onpick)   # 綁定'pick_event'事件到 onpick(event)

用滑鼠選取一個數據點,會顯示數據點的資料
def onclick(event):   # 定義一個事件觸發後的行為 onclick(event)-顯示滑鼠點選的位置
    plt.gca().set_title('Event at pixels {},{} \nand data {},{}'.format(event.x, event.y, event.xdata, event.ydata))
plt.gcf().canvas.mpl_connect('button_press_event', onclick)   # 綁定'button_press_event'事件到 onclick(event)
用滑鼠點選一個圖中任何地方,會顯示該位置的坐標

2. 溫習功課所學

很多重要的數據處理都是第一課 <Link Here> 中所講解的。見新聞也講起最近的酷熱天氣,所以試用香港數據再演繹一份有關極端天氣的 matplotlib 功課,資料來源是NOAA的香港國際機場氣象站的資料 ( https://www7.ncdc.noaa.gov/CDO/cdodateoutmod.cmd),當中的每日最高/最低溫度。結果可見近日的確連續數天突破往年同期的高溫記錄, 但之後的7-8月才是傳統上最熱的月份,還有一段炎熱日子要過呢⋯⋯

df = pd.read_csv('data/CDO2930757646668.csv')

# Data Cleaning, Rename Columns
df.rename(columns=lambda x: x.replace(' ', ''), inplace=True)
columns_to_keep = ['YEARMODA', 'MAX', 'MIN']
df = df[columns_to_keep]
df.rename(columns={"YEARMODA": "Date", "MAX": "Tmax", "MIN": "Tmin", }, inplace=True)
df.replace({'\*': ''}, inplace=True, regex=True)

# 日期、最高氣溫(攝氏)、最低氣溫(攝氏),去除潤年29/02
df['Date'] = pd.to_datetime(df['Date'], format="%Y%m%d")
df['Tmax'] = (pd.to_numeric(df['Tmax']) -32) *5/9
df['Tmin'] = (pd.to_numeric(df['Tmin']) -32) *5/9
df = df [df['Date'].map(lambda d: not ((d.month == 2) and (d.day == 29)))]  # 去除潤年的29/02數據
#df.columns.values.tolist()
#df.head()

# 複製出2018年的 Dataframe
df_2018 = (df[df['Date'].map(lambda d: d.year == 2018)]
           .groupby(df['Date'].map(lambda d: d.timetuple().tm_yday))  # Groupby 該日子在一年中的日期 range [1, 366]
           .apply(lambda sdf: pd.Series({'Tmax': sdf['Tmax'].max(),
                                         'Tmin': sdf['Tmin'].min()}))   # 定義 Tmax 和 Tmin 欄位
           .sort_index()
           .copy(True)
          )

# 複製出2018年以外年份的 Dataframe
df_other = (df[df['Date'].map(lambda d: d.year != 2018)]
           .groupby(df['Date'].map(lambda d: d.timetuple().tm_yday))  # Groupby 該日子在一年中的日期 range [1, 366]
           .apply(lambda sdf: pd.Series({'Tmax': sdf['Tmax'].max(),
                                         'Tmin': sdf['Tmin'].min()})) # 定義 Tmax 和 Tmin 欄位,每個日子的最大/最小值
           .sort_index()
           .copy(True)
          )

# 組合數列,複製出2018年大於 other's Max 和小於 other's Min 的日子
df = df_2018.merge(df_other, left_index=True, right_index=True, suffixes=('_2018', '_other'))
df_2018_low = df[df['Tmin_2018'] < df['Tmin_other']]['Tmin_2018']
df_2018_high = df[df['Tmax_2018'] > df['Tmax_other']]['Tmax_2018']
# df.head()

#   開始製圖
%matplotlib inline
fig = plt.figure(figsize=(16, 8))

#   繪製其他年份的 高/低 溫度範圍
low = plt.plot(df_other.index, df_other['Tmin'], label='Daily Minimum 2008-2017', lw=3, alpha=0.6)
high = plt.plot(df_other.index, df_other['Tmax'], label='Daily Maximum 2008-2017', lw=3, alpha=0.6)
ax = plt.gca()
shade = ax.fill_between(df_other.index, df_other['Tmin'], df_other['Tmax'], facecolor='lightgrey', alpha =0.25)

#   繪製2018年的極端氣溫
scater_high = plt.scatter(df_2018_high.index, df_2018_high, label='2018 Maximum Breakout',
                          marker='+', color='r', s=60, alpha=0.8)
scater_low = plt.scatter(df_2018_low.index, df_2018_low, label='2018 Minimum Breakout',
                         marker='_', color='purple', s=60, alpha=0.8)

#   加入圖例
legend = plt.legend(bbox_to_anchor=(0.1, .95, 0.8, .95), loc=3, ncol=4, mode='expand',
    handlelength=3, scatterpoints=3)   # 設定圖例框
legend.get_frame().set_alpha(0.)  # 將圖例框透明化

#   新 x軸標記- 每月範圍和月份字串
month_day = [0] + list(np.cumsum(pd.date_range('2008-01-01', periods=12, freq='M').map(lambda d: d.day)))
day_anno_pos = list(map(np.mean, zip(month_day[: -1], month_day[1: ])))   # 準備標記位置
month_label = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']     # 準備月份字串
plt.vlines(month_day[1:-1], *ax.get_ylim(), color='k', linestyles='--', lw=0.3, alpha=0.2)    # 圖中的格線(垂直)
for pos, text in zip(day_anno_pos, month_label):
    ax.annotate(s=text, xy =(pos, 0), xycoords='data', alpha=0.8, size=12,
                verticalalignment='top', horizontalalignment='center' , rotation=0)  # 每月範圍的月份字串
  
#   新 y軸標記 - 溫度
temp_anno_pos = [0, 5, 10, 15, 20, 25, 30, 35]   # 準備標記位置
temp_anno_label = list(map(lambda t: '{}$^{{\circ}}$C'.format(int(t)), temp_anno_pos))    # 準備溫度標記
plt.hlines(temp_anno_pos, *ax.get_xlim(), color='k', linestyles='--', lw=0.3, alpha=0.2)    # 圖中的格線(水平)
for pos, text in zip(temp_anno_pos, temp_anno_label):
    ax.annotate(s=text, xy =(-2, pos), xycoords='data', alpha=0.8, size=12,
                verticalalignment='center', horizontalalignment='right' , rotation=0) # 放置溫度標記

#   加入標題
plt.title('Temperature From 2008 To 2018 Near Hong Kong Intl', size=18, alpha=0.8)

#   隱藏圖框
for spine in ax.spines.values():
    spine.set_visible(False)
plt.tick_params(top='off', bottom='off', left='off', right='off', labelleft='off', labelbottom='off')


沒有留言:

張貼留言