# 交通時空大數據_基礎
> 須預先下載的Modules:
> `pip install matplotlib`、`pip install pandas`、`pip install geopandas`
## 整理出需要的data
> 若資料集有太多不需要的資訊,可以進行這一步驟整理資料
> 若不整理也可以直接使用的程式碼
* 車輛ID `car_id`
* 時間 `_time`
* 經度 `lon`
* 緯度 `lat`
* 速度 `cal_spd`
```python=
import os
import shutil
import pandas as pd
# 指定資料夾路徑
input_folder = 'C:\\NCKU\\data_analyze\\NSYU_september\\send'
output_folder = 'C:\\NCKU\\data_analyze\\NSYU_september\\new'
# 確保輸出資料夾存在,若不存在就建立
os.makedirs(output_folder, exist_ok=True)
# 遍歷資料夾中的所有檔案
for filename in os.listdir(input_folder):
if filename.endswith('.csv'):
input_filepath = os.path.join(input_folder, filename)
data = pd.read_csv(input_filepath)
selected_data = data[['car_id', '_time', 'lon', 'lat', 'cal_spd']]
output_filepath = os.path.join(output_folder, filename)
# 將整理後的資料另存為檔案
selected_data.to_csv(output_filepath, index=False)
print("finish")
```
---
## 時間完整性評估
> hourly_data_statistics.py
### 處理時間
* 使用日期格式轉換
```python
data['Hour'] = pd.to_datetime(data['_time']).dt.hour
```
* 使用字串分割
> 日期格式是:2020-09-04 23:53:44
> 索引從0開始,故小時(23)為11(包括)~13(不包括)
```python
data['Hour'] = data['_time'].str.slice(11, 13)
```
* 使用apply方法 (須較長的處理時間)
> 定義一function,會對dataframe的每列進行操作
```python
def f(r):
return r[11:13]
data['Hour'] = data['_time'].apply(f).astype(int)
```
> 亦可使用lambda匿名函數
> `axis=1` 表示你要對每一行應用 lambda 函數
```python
data['Hour'] = data.apply(lambda row: row['_time'][11:13], axis=1)
```
* 完整程式碼
> `.astype(int)` 將結果轉換為整數型別
> 字串分割、
```python=
import os
import pandas as pd
# 指定資料路徑
input_data = 'C:\\NCKU\\data_analyze\\NSYU_september\\new\\merge0509.csv'
data = pd.read_csv(input_data)
# 處理時間
# 使用日期格式轉換
data['Hour'] = pd.to_datetime(data['_time']).dt.hour
# 使用字串分割
data['Hour'] = data['_time'].str.slice(11, 13).astype(int)
# 使用apply方法
def f(r):
return r[11:13]
data['Hour'] = data['_time'].apply(f).astype(int)
#lambda
data['Hour'] = data.apply(lambda row: row['_time'][11:13], axis=1).astype(int)
print(data)
```
---
### 統計每小時的數據量
> 統計每個小時內有多少唯一的 `'car_id'` (同一台車不重複計算)
* 使用 `groupby()` ,按照 `'Hour'` 欄位對資料進行分組
* `reset_index()` 重置索引,使 `'Hour'` 成為一列
* `transpose()`將資料表轉置(可以不用)
```python=
Hourcount = data.groupby('Hour')['car_id'].count()
Hourcount = Hourcount.rename('count').reset_index()
print(Hourcount.transpose())
```

### 繪製圖表
```python=
# 繪製圖表
# 建立圖表:fig = plt.figure(尺寸, dpi=大小)
fig = plt.figure(figsize=(8, 4), dpi=100)
# 建立子圖ax = fig.add_subplot(111)
ax = fig.add_subplot(111)
# 111 : nrows x ncols x index(將圖表分割成 1 行 1 列的網格(1x1),並選擇其中的第一個子圖)
# plt.plot(x軸, y軸, 樣式)
plt.plot(Hourcount['Hour'], Hourcount['count'], 'k-') # 折線
plt.plot(Hourcount['Hour'], Hourcount['count'], 'k.') # 散點
plt.bar(Hourcount['Hour'], Hourcount['count']) # 柱狀
plt.ylabel("Data Volume")
plt.xlabel("Hour")
# plt.xticks(要放幾個刻度, 刻度的標籤(X 軸上的數字))
plt.xticks(range(24), range(24))
plt.title("Hourly data volume")
# plt.ylim(Y軸的下限, Y軸的上限)
plt.ylim(0, 20000)
plt.show()
```

### 完整程式碼
```python=
import os
import pandas as pd
import matplotlib.pyplot as plt
# 指定資料路徑
input_data = 'merge0509.csv'
data = pd.read_csv(input_data)
# 處理時間
data['Hour'] = data['_time'].str.slice(11, 13).astype(int)
# 每小時的數據量
Hourcount = data.groupby('Hour')['car_id'].count()
Hourcount = Hourcount.rename('count').reset_index()
print(Hourcount.transpose())
# 繪製圖表
# 建立圖表:fig = plt.figure(尺寸, dpi=大小)
fig = plt.figure(figsize=(8, 4), dpi=100)
# 建立子圖ax = fig.add_subplot(111)
ax = fig.add_subplot(111)
# 111 : nrows x ncols x index(將圖表分割成 1 行 1 列的網格(1x1),並選擇其中的第一個子圖)
# plt.plot(x軸, y軸, 樣式)
plt.plot(Hourcount['Hour'], Hourcount['count'], 'k-') # 折線
plt.plot(Hourcount['Hour'], Hourcount['count'], 'k.') # 散點
plt.bar(Hourcount['Hour'], Hourcount['count']) # 柱狀
plt.ylabel("Data Volume")
plt.xlabel("Hour")
# plt.xticks(要放幾個刻度, 刻度的標籤(X 軸上的數字))
plt.xticks(range(24), range(24))
plt.title("Hourly data volume")
# plt.ylim(Y軸的下限, Y軸的上限)
plt.ylim(0, 20000)
plt.show()
```
---
## 空間完整性評估
### 建立空間分布柵格圖
> spatial_distribution_grid_map.py
> 須預先下載的Modules:`pip install keplergl`、`pip install -U transbigdata`

```python=
import geopandas as gpd
import transbigdata as tbd
import pandas as pd
import matplotlib.pyplot as plt
input_csv = 'merge0509.csv'
input_shp = 'shp\\kuohsiung.shp'
data = pd.read_csv(input_csv, dtype={'car_id': int, '_time': str, 'lon': float, 'lat': float, 'cal_spd': float})
sz = gpd.read_file(input_shp)
sz = sz.to_crs(epsg=4326) #一定要用WGS84
# 篩選剔除研究範圍外之數據
# accuracy為柵格大小,單位為m -> 越小精度越高
accuracy = 500
data = tbd.clean_outofshape(data, sz, col=['lon', 'lat'], accuracy = accuracy)
# 定義範圍、柵格參數(需用WGS84)
minx, miny, maxx, maxy = sz.total_bounds
bound = [minx, miny, maxx, maxy]
params = tbd.area_to_params(bound, accuracy = accuracy)
print(params)
#將GPS資料對應至柵格
data['LonCol'], data['LatCol'] = tbd.GPS_to_grid(data['lon'], data['lat'], params)
grid_agg = data.groupby(['LonCol', 'LatCol'])['car_id'].count().reset_index()
grid_agg['geometry'] = tbd.grid_to_polygon([grid_agg['LonCol'], grid_agg['LatCol']], params)
grid_agg = gpd.GeoDataFrame(grid_agg)
fig = plt.figure(1, figsize=(8, 8), dpi=100)
ax = fig.add_subplot(111)
# tbd.plot_map(plt, bound, zoom = 11, style = 11) 界接地圖
sz.plot(ax = ax, edgecolor = (0,0,0,0), facecolor = (0,0,0,0.1), linewidth = 0.5)
cax = plt.axes([0.1, 0.33, 0.02, 0.3])
plt.title('Data count')
plt.sca(ax)
grid_agg.plot(column = 'car_id', cmap = 'autumn_r', ax=ax, cax = cax, legend=True)
tbd.plotscale(ax, bounds = bound, textsize = 10, compasssize = 1, accuracy = 2000, rect = [0.6, 0.03], zorder = 10)
plt.axis('off')
plt.xlim(bound[0], bound[2])
plt.ylim(bound[1], bound[3])
plt.show()
```
### 建立空間分布散點圖
> spatial_distribution_scatter_plot.py
> 須預先下載的Modules:`pip install seaborn`

```python=
import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib as mpl
import transbigdata as tbd
input_csv = 'merge0509.csv'
input_shp = 'shp\\kuohsiung.shp'
data = pd.read_csv(input_csv, dtype={'car_id': int, '_time': str, 'lon': float, 'lat': float, 'cal_spd': float})
sz = gpd.read_file(input_shp)
sz = sz.to_crs(epsg=4326)
#经纬度小数点保留三位小数
data = data[['lon','lat']].round(3).copy()
# 集計每個小範圍內的數據量
data['count'] = 1
data = data.groupby(['lon', 'lat'])['count'].count().reset_index()
# 排序數據,讓數據量小的放在最上面畫,數據大的放在最下面最後畫
data = data.sort_values(by='count')
fig = plt.figure(1, (8, 8), dpi=80)
ax = plt.subplot(111)
plt.sca(ax)
# 繪製行政區劃的邊界
minx, miny, maxx, maxy = sz.total_bounds
bounds = [minx, miny, maxx, maxy]
sz.plot(ax=ax, edgecolor=(0, 0, 0, 0), facecolor=(0, 0, 0, 0.1), linewidths=0.5)
# 定義 colorbar
pallete_name = "BuPu"
colors = sns.color_palette(pallete_name, 3)
colors.reverse()
cmap = mpl.colors.LinearSegmentedColormap.from_list(pallete_name, colors)
vmax = data['count'].quantile(0.99)
norm = mpl.colors.Normalize(vmin=0, vmax=vmax)
# 繪製散點圖
plt.scatter(data['lon'], data['lat'], s=1, alpha=1, c=data['count'], cmap=cmap, norm=norm)
# 添加比例尺和指北針
tbd.plotscale(ax, bounds=bounds, textsize=10, compasssize=1, accuracy=2000, rect=[0.6, 0.03])
plt.axis('off')
plt.xlim(bounds[0], bounds[2])
plt.ylim(bounds[1], bounds[3])
# 繪製 colorbar
cax = plt.axes([0.15, 0.33, 0.02, 0.3])
plt.colorbar(cax=cax)
plt.title('count')
plt.show()
```
---
### 建立空間分布熱力圖
> spatial_distribution_heatmap.py

```python=
import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import transbigdata as tbd
import numpy as np
# 指定文件路径
input_csv = 'merge0509.csv'
input_shp = 'shp\\kuohsiung.shp'
data = pd.read_csv(input_csv, dtype={'car_id': int, '_time': str, 'lon': float, 'lat': float, 'cal_spd': float})
sz = gpd.read_file(input_shp)
sz = sz.to_crs(epsg=4326)
# 經緯度小數點保留三位小數
data = data[['lon','lat']].round(3).copy()
# 集計每個小範圍內的數據量
data['count'] = 1
data = data.groupby(['lon', 'lat'])['count'].count().reset_index()
data = data.sort_values(by='count')
# 轉換數據透視表,變成矩陣格式
d = data.pivot(columns = 'lon',index = 'lat',values = 'count').fillna(0)
# 取對數,縮小最大最小值之間的差距
z = np.log(d.values)
x = d.columns
y = d.index
levels = np.linspace(0, z.max(), 25)
fig = plt.figure(1, (8, 8), dpi=80)
ax = plt.subplot(111)
plt.sca(ax)
fig.tight_layout(rect=(0.05, 0.1, 1, 0.9)) # 調整整體空白
# 繪製行政區劃的邊界
minx, miny, maxx, maxy = sz.total_bounds
bounds = [minx, miny, maxx, maxy]
sz.plot(ax=ax, edgecolor=(0, 0, 0, 0), facecolor=(0, 0, 0, 0.1), linewidths=0.5)
# 定義colorbar
cmap = mpl.colors.LinearSegmentedColormap.from_list('cmap', ['#9DCC42', '#FFFE03', '#F7941D', '#E9420E', '#FF0000'], 256)
# 繪製等高線圖
plt.contourf(x, y, z, levels=levels, cmap=cmap, origin='lower')
# 添加比例尺和指北針
tbd.plotscale(ax, bounds=bounds, textsize=10, compasssize=1, accuracy=2000, rect=[0.6, 0.03])
plt.axis('off')
plt.xlim(bounds[0], bounds[2])
plt.ylim(bounds[1], bounds[3])
# 繪製colorbar
cax = plt.axes([0.13, 0.32, 0.02, 0.3])
cbar = plt.colorbar(cax=cax)
# cbar = plt.colorbar(cax=cax, ticks=np.linspace(z.min(), z.max(), 5), format="%1.1f")
# 調整colorbar的顯示標記位置
val = [1, 10, 100, 1000]
pos = np.log(np.array(val))
# 在什麼位置顯示標記
cbar.set_ticks(pos)
# 標記顯示什麼內容
cbar.set_ticklabels(val)
plt.title('Count')
plt.show()
```
---
### 建立空間核密度
> spatial_kernel_density.py

```python=
import geopandas as gpd
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import transbigdata as tbd
import seaborn as sns
import numpy as np
input_csv = 'merge0509.csv'
input_shp = 'shp\\kuohsiung.shp'
data = pd.read_csv(input_csv, dtype={'car_id': int, '_time': str, 'lon': float, 'lat': float, 'cal_spd': float})
sz = gpd.read_file(input_shp)
sz = sz.to_crs(epsg=4326)
data = data[['lon','lat']].round(3).copy()
data['count'] = 1
data = data.groupby(['lon', 'lat'])['count'].count().reset_index()
# 建立畫布
fig = plt.figure(1, (10, 10), dpi=60)
ax = plt.subplot(111)
plt.sca(ax)
fig.tight_layout(rect=(0.05, 0.1, 1, 0.9)) # 調整整體空白
# 繪製行政區劃的邊界
minx, miny, maxx, maxy = sz.total_bounds
bounds = [minx, miny, maxx, maxy]
sz.plot(ax=ax, edgecolor=(0, 0, 0, 0), facecolor=(0, 0, 0, 0.1), linewidths=0.5)
# 色彩映射的數據
cmap = mpl.colors.LinearSegmentedColormap.from_list('cmap', ['#9DCC42', '#FFFE03', '#F7941D', '#E9420E', '#FF0000'], 256)
# 設定顯示範圍
plt.axis('off')
plt.xlim(bounds[0], bounds[2])
plt.ylim(bounds[1], bounds[3])
# 定義 colorbar 位置
cax = plt.axes([0.13, 0.32, 0.02, 0.3])
# 繪製二維核密度圖
sns.kdeplot(x='lon', y='lat', # 指定x與y座標所在的欄位
data=data[data['count'] > 1], # 來源資料,篩選去除太小的值
weights='count', # 設定權重所在欄位
alpha=0.8, # 透明度
gridsize=120, # 繪圖精細度,越高越慢
bw=0.03, # 高斯核大小(經緯度),越小越精細
cmap=cmap, # 定義色彩映射
ax=ax, # 指定繪圖位置
shade=True, # 是否填充等高線間的顏色
shade_lowest=False, # 最底層不顯示顏色
cbar=True, # 顯示 colorbar
cbar_ax=cax # 指定 colorbar 位置
)
# 顯示圖形
plt.show()
```
---
## Seaborn
> Seaborn是基於matplotlib的Python視覺化函式庫
* sns.kdeplot() https://seaborn.pydata.org/generated/seaborn.kdeplot.html
* 來ChatGPT的介紹與作者的補充:)
`sns.kdeplot()` 是 seaborn 庫中的一個函數,用於繪製單變量或雙變量的核密度估計(Kernel Density Estimation)。核密度估計是一種用來估計概率密度函數的非參數方法,它會通過對數據進行平滑處理來估計數據的分佈情況。
`sns.kdeplot()` 可以繪製單變量數據的核密度估計曲線,也可以繪製雙變量數據的核密度等值線圖或二維核密度圖。它的用法如下:
```python
sns.kdeplot(data, data2=None, shade=False, vertical=False, kernel='gau', bw='scott', gridsize=100, cut=3, clip=None, legend=True, cumulative=False, shade_lowest=True, cbar=False, cbar_ax=None, cbar_kws=None, ax=None, **kwargs)
```
主要參數包括:
- `data`: 要繪製的數據,可以是一維或二維數組。
- `data2`: 用於繪製雙變量核密度估計的第二組數據(可選)。
- `shade`: 是否給密度曲線下方區域填充顏色,默認為 False。
- `vertical`: 是否將密度估計曲線繪製為垂直方向,默認為 False。
- `kernel`: 用於估計密度的核函數,可選值包括 'gau'(高斯核)、'cos'(餘弦核)、'biw'(雙重三角核)等,默認為 'gau'。
- ==即為kernel shape==
- `'biw'` 等同GIS的 Quartic
- `'tri'` 等同GIS的 Triangular
- `'epa'` 等同GIS的 Epanechnikov
- `'uni'` 等同GIS的 Uniform
- `'triw'` 等同GIS的 Triweight
- `bw`: 控制核密度估計帶寬的方法,可選值包括 'scott'、'silverman' 或一個標量,默認為 'scott'。
- ==即為Radius, 單位則依輸入的資料而定==
- 因這邊輸入的值是經緯度,所以這邊的單位都是度,如需使用公尺等單位,可能須再自行轉換
- `gridsize`: 用於計算核密度的網格大小,默認為 100。
- `cut`: 在繪製雙變量核密度估計時,用於裁剪數據的標準差數,默認為 3。
- `clip`: 用於裁剪核密度估計的範圍。
- `legend`: 是否顯示圖例,默認為 True。
- `cumulative`: 是否繪製累積密度圖,默認為 False。
- `shade_lowest`: 是否給最低密度區域填充顏色,默認為 True。
- `cbar`: 是否在繪製雙變量核密度圖時繪製顏色條,默認為 False。
- `cbar_ax`: 顏色條的 Axes 對象。
- `cbar_kws`: 顏色條的其他參數。
- `ax`: 繪製圖形的 Axes 對象,如果沒有提供,則使用當前 Axes。
`sns.kdeplot()` 可以幫助你快速了解數據的分佈情況,並且支持許多參數來定制化你的圖形。
---