# 使用 Pandas 從網頁讀取衛星軌道資料並儲存成 csv 檔
> 作者:王一哲
> 日期:2021/7/20
## 前言
我以前寫過一篇關於[克卜勒第三行星運動定律](https://hackmd.io/@yizhewang/SJxnasj9S)的文章,為了將各行星的衛星資料整理成可用的 csv 檔,當時是將表格複製到 LibreOffice Calc 裡面再手動整理,但是使用 Pandas 可以更自動化,以下是我試出來的方法。
<br />
## 程式碼
為了避免找不到儲存後的 csv 檔案,建議於 Python Shell 中用以下指令查詢目前所在的路徑。
```python
import os
os.getcwd()
```
如果使用 Python 預設的 IDLE,在 Windows 10 預設的路徑是
```
C:\Users\[UserName]\AppData\Local\Programs\Python\[PythonXX]
```
在 Ubuntu 預設的路徑是
```
/home/[UserName]
```
如果在 Windows 10 可以用以下指令將路徑切換到桌面
```python
os.chdir("C:\\Users\\[UserName]\\Desktop")
```
<br />
接下來從 [Jovian Satellite Fact Sheet](https://nssdc.gsfc.nasa.gov/planetary/factsheet/joviansatfact.html) 網頁的表格中取得木星衛星軌道資料,在網頁上點擊滑鼠右鍵**檢視網頁原始碼**,於原始碼中搜尋**table class**會看到有兩個表格,名稱分別為 **bulk** 及 **orbital**,我們需要的是衛星軌道資料,因此讀取時需要加上 **attrs={'class':'orbital'}**;為了將最上面的列當作欄位標題,需要加上 **header=0**。
```python
import pandas as pd
table = pd.read_html('https://nssdc.gsfc.nasa.gov/planetary/factsheet/joviansatfact.html', attrs={'class':'orbital'}, header=0)
```
<br />
將資料複製到 data 中,格式為 pandas.DataFrame,再將 data 儲存成 data.csv。
```python
data = table[0].copy()
data.to_csv('data.csv')
```
<br />
如果用文字編輯器打開 data.csv 會看到以下的檔案。
```
,Unnamed: 0,Semi-major axis(103km),Semi-major axis(Jovian Radii),OrbitalPeriod*(days),RotationPeriod(days),Inclination(degrees),Eccentricity
0,,,,,,,
1,Galilean Satellites,,,,,,
2,Io (JI),421.8,5.91,1.769138,S,0.04,0.004
3,Europa (JII),671.1,9.4,3.551181,S,0.47,0.009000000000000001
4,Ganymede (JIII),1070.4,14.97,7.154553,S,0.18,0.001
5,Callisto (JIV),1882.7,26.33,16.689017,S,0.19,0.006999999999999999
...
92,S/2018 J1,11483,160.6,252.0,,30.61,0.094
```
<br />
從 csv 檔中可以看到每個欄位的名稱,如果想要直接在 Python Shell 裡印出欄位名稱可以使用以下的指令。
```python
for col in data.columns: print(col)
```
<br />
目前的欄位名稱為
```
Unnamed: 0
Semi-major axis(103km)
Semi-major axis(Jovian Radii)
OrbitalPeriod*(days)
RotationPeriod(days)
Inclination(degrees)
Eccentricity
```
由於網頁的表格中,最左側的欄位沒有標題,pandas 會將這欄的名稱設定為 **Unnamed: 0**,使用以下指令將此欄的名稱改為 **Name**,指令最後的 **inplace=True** 是為了將修改後的資料儲存到 data 當中。
```python
data.rename(columns={'Unnamed: 0': 'Name'}, inplace=True)
```
<br />
由於表格中有一些空白列,還有4列只有衛星分類的名稱 Galilean Satellites、Lesser Satellites、Unnamed Satellites、Recently Discovered Satellites,可以用以下的指令刪除這些資料。
```python
data.dropna(subset=['Semi-major axis(103km)'], inplace=True)
```
如果只要刪除整列都是空白的資料,語法為
```python
data.dropna(how='all', inplace=True)
```
<br />
由於 **Name** 欄位的某些資料有**逗號**,雖然可以儲存成 csv 檔,但是要再從 csv 檔讀取資料時會遇到麻煩,需要先刪除這些逗號。
```python
data['Name'] = data['Name'].replace(',', '', regex=True)
```
<br />
在欄位 **OrbitalPeriod\*(days)** 當中有許多的資料數字後面多了一個字母 R,這是網頁上用來標示該衛星為公轉方向為逆行 (retrograde),但是我只想留下數字,並在 data 最後面新增欄位記錄這個衛星是否為逆行。使用以下指令新增欄位 **Retrograde**,並將值設定為 **False**。
```python
data['Retrograde'] = False
```
找出 **OrbitalPeriod\*(days)** 欄位資料**以 R 結尾**的列,並將 **Retrograde** 欄位資料改為 **True**,如果沒有先新增欄位並設定預設值而是直接使用以下指令,則其它列的 **Retrograde** 欄位值為 **NaN**。
```python
data.loc[data['OrbitalPeriod*(days)'].str.endswith('R'), 'Retrograde'] = True
```
刪除 **OrbitalPeriod\*(days)** 欄位資料**結尾的R**。
```python
data['OrbitalPeriod*(days)'] = data['OrbitalPeriod*(days)'].map(lambda x: x.rstrip('R'))
```
<br />
以下是完整的程式碼。
```python=
import pandas as pd
planet = 'Juipter'
table = pd.read_html('https://nssdc.gsfc.nasa.gov/planetary/factsheet/joviansatfact.html', attrs={'class':'orbital'}, header=0)
data = table[0].copy()
data.rename(columns={'Unnamed: 0': 'Name'}, inplace=True)
data.dropna(subset=['Semi-major axis(103km)'], inplace=True)
data['Name'] = data['Name'].replace(',', '', regex=True)
data['Retrograde'] = False
data.loc[data['OrbitalPeriod*(days)'].str.endswith('R'), 'Retrograde'] = True
data['OrbitalPeriod*(days)'] = data['OrbitalPeriod*(days)'].map(lambda x: x.rstrip('R'))
data.to_csv(planet+'SatelliteData.csv')
```
<br />
## 結語
因為木星、土星、天王星的衛星資料網頁格式幾乎一樣,只要將行星的名稱及讀取資料的網址改掉,就可以將土星、天王星的衛星資料也儲存成 csv 檔。以下是木星、土星、天王星的衛星 log a - log T 關係圖,斜率都很接近 2/3。
<img height="60%" width="60%" src="https://i.imgur.com/PrEtuWI.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<div style="text-align:center">木星衛星 log a - log T 關係圖</div>
<br />
<img height="60%" width="60%" src="https://i.imgur.com/57E0QsX.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<div style="text-align:center">土星衛星 log a - log T 關係圖</div>
<br />
<img height="60%" width="60%" src="https://i.imgur.com/H25173p.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<div style="text-align:center">天王星衛星 log a - log T 關係圖</div>
<br />
## 行星衛星資料
1. [Jovian Satellite Fact Sheet](https://nssdc.gsfc.nasa.gov/planetary/factsheet/joviansatfact.html)
2. [Saturnian Satellite Fact Sheet](https://nssdc.gsfc.nasa.gov/planetary/factsheet/saturniansatfact.html)
3. [Uranian Satellite Fact Sheet](https://nssdc.gsfc.nasa.gov/planetary/factsheet/uraniansatfact.html)
<br />
## 參考資料
1. https://stackoverflow.com/questions/8248397/how-to-know-change-current-directory-in-python-shell
2. https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_html.html
3. https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.rename.html
4. https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.dropna.html
5. https://stackoverflow.com/questions/56947333/how-to-remove-commas-from-all-the-column-in-pandas-at-once
6. https://stackoverflow.com/questions/50372272/how-to-add-columns-to-an-empty-pandas-dataframe
7. https://pandas.pydata.org/docs/reference/api/pandas.Series.str.endswith.html
8. https://stackoverflow.com/questions/13682044/remove-unwanted-parts-from-strings-in-a-column
<br />
---
**2021/7/21 補充**
由於[海王星衛星資料](https://nssdc.gsfc.nasa.gov/planetary/factsheet/neptuniansatfact.html)的表格比較不一樣,需要稍微修改一下程式碼,忽略表格最上面的列,再手動為每個欄命名,主要修改的部分是第4、5、7、8行程式碼。
```python=
import pandas as pd
planet = 'Neptune'
table = pd.read_html('https://nssdc.gsfc.nasa.gov/planetary/factsheet/neptuniansatfact.html',
attrs={'class':'orbital'}, skiprows=1)
data = table[0].copy()
data.columns = ['Name', 'Semi-major axis(103km)', 'Semi-major axis(Jovian Radii)', 'OrbitalPeriod*(days)',
'RotationPeriod(days)', 'Inclination(degrees)', 'Eccentricity']
data.dropna(subset=['Name'], inplace=True)
data['Name'] = data['Name'].replace(',', '', regex=True)
data['Retrograde'] = False
data.loc[data['OrbitalPeriod*(days)'].str.endswith('R'), 'Retrograde'] = True
data['OrbitalPeriod*(days)'] = data['OrbitalPeriod*(days)'].map(lambda x: x.rstrip('R'))
data.to_csv(planet+'SatelliteData.csv')
```
<br />
<img height="60%" width="60%" src="https://i.imgur.com/bXF8Of1.png" style="display: block; margin-left: auto; margin-right: auto;"/>
<div style="text-align:center">海王星衛星 log a - log T 關係圖</div>
<br />
---
###### tags:`Python`