Python備忘録

# 概要
Pythonでやった作業について，忘れないようまとめます。
※Pythonの学習中に行ったことなので、もっと便利な関数や手法があるかもしれません。

# ファイルパスの操作（OSモジュールを使用）

## ファイル名（拡張子付き）の取り出し
```python
import os
fpath="aaa/bbb/ccc/ddd.png"
fname=os.path.basename(fpath)
# ddd
```
## ファイル名（拡張子なし）の取り出し
```python
import os
fpath="aaa/bbb/ccc/ddd.png"
fname=os.path.splitext(os.path.basename(fpath))[0]
# ddd
```
## 拡張子のみの取り出し
```python
import os
fpath="aaa/bbb/ccc/ddd.png"
fname=os.path.splitext(os.path.basename(fpath))[1]
# .png
```

## ディレクトリパスの取り出し
```python
import os
fpath="aaa/bbb/ccc/ddd.png"
fname=os.path.dirname(fpath)
# aaa/bbb/ccc
```
# ファイルパスからファイル名のみを抜き出す(OSモジュールを使用せず)

## splitを使う
```python
fpath="aaa/bbb/ccc/ddd.png"
f_split=fpath.split('/')
fname=f_split[-1]
```
`split()`で区切り文字で分割，分割した配列の最後がファイル名．
区切り文字はあらかじめ調べるor決める必要がある
参考：[javascript（nodejs）でフルパスからファイル名のみを取得する方法 - Nodachisoft](https://nodachisoft.com/common/jp/article/jp000166/)
## 正規表現を使う
```python
import re
fpath="aaa/bbb/ccc/ddd.png"
m = re.match(r".+[\\/](.+?)([-_.]*)?$",fpath)  
print(m[1])
# ddd.png
```
参考：[Pythonでパス文字列からファイル名・フォルダ名・拡張子を取得、結合 | note.nkmk.me](https://note.nkmk.me/python-os-basename-dirname-split-splitext/)

# ファイル読み書きテンプレ
## JSON
```python
#.jsonの読み取り
import json
with open("test_r.json","r",encoding="utf-8") as f:
    d_read=json.load(f)
#.jsonへ書き込み
with open("test_w.json", 'w',encoding="utf-8") as f:
    json.dump(d_write, f, indent=2,ensure_ascii=False)    
```
保存時、`indent`を付けることでインデントが付き、`ensure_ascii`
# DataFrameのよく使う？操作
## NaN埋め、型変換
指定した列のみを型変換する場合
```python
l_cols=['A','B','C']
d_rep={}
for col in ctg_columns:
    d_rep[col]=str
df=df.astpye(d_rep)
```
`列名:型`の辞書型を作成し、`.astype`に指定する。
`.fillna`も同じ手法でできる
```python
l_cols=['A','B','C']
d_rep={}
for col in ctg_columns:
    d_rep[col]=0
df=df.fillna(d_rep)
```
## df.groupbyをグループごとにfor文で回す
```python
import pandas as pd
for idx,df_group in df.groupby(["馬番","着順"]):
    # 何かの処理
    print(idx)
```
# 正規表現の利用
## 正規表現で途中にある数値を取得
`re.search`を使うことで文字列の途中からマッチする。
```python
import re
fname="hogehoge24.png"
m=re.match(r"[0-9]{1,2}",fname)
print(m[0])
#24
```
ファイル名のパターンが決まっているなら、間の文字列を抜き出すことでも取り出せる。
「(.+)」で任意の１文字以上の文字列にマッチする。
```python
import re
fname="hogehoge24.png"
m=re.match(r"[0-9]{1,2}",fname)
m=re.search(r"hogehoge(.+)\.png",fname) 
print(m[1])
#24
```

`re.match`だと先頭からのマッチとなり、途中にあるパターンを抜き出せない。
参考：[【python】re.match より re.search を使おう](https://qiita.com/halhorn/items/9e6599a3422e7dd94ec8)
　　　[正規表現でファイルの拡張子を取り除く方法。](https://www.almondlab.jp/notes/541)
## datetimeをdatetimeに変換
Javascriptから取得した時刻をPythonのdatetimeに変換する
```python:convert_datetime.py
import re
from datetime import datetime
time_JS="2022-12-28T12:58:30+09:00"
# 年・月・日を取得
m=re.search("([0-9]{4})-([0-9]{2})-([0-9]{2})",time_JS)
# 時・分・日を取得
m2=re.search("([0-9]{2}):([0-9]{2}):([0-9]{2})",time_JS)
# Pythonのdatetimeに変換
res_datetime=datetime(int(m[1]),int(m[2]),int(m[3]),int(m2[1]),int(m2[2]),int(m2[3]))
print(res_datetime)
```
# Excelファイルの処理

DataframeをXlsxファイルへと書き出す

## 複数のDataFrameをXlsxに出力するクラス

クラス内変数の辞書型配列に、Key＝シート名、中身＝保存したいDataFrameで登録した情報をもとにxlsxファイルを作成します。

```python:exportXlsx.py
class exportXlsx:
    def __init__(self) -> None:
        self.dict_array={
            
        }
        pass
    def addSheet(self,item,sname):
        # シート名としてDFを保存する
        self.dict_array[sname]=item
        
    def save(self,savefpath):
        # 登録したDFを保存する
        
        dict_keys=list(self.dict_array.keys())
        # 先頭のシートで上書き
        logger.info("save:"+savefpath)
        logger.debug("save:"+dict_keys[0])
        self.dict_array[dict_keys[0]].to_excel(savefpath,index=False,sheet_name=dict_keys[0])
        # 残りのシートを追加
        with pd.ExcelWriter(savefpath, mode='a') as writer:
            for key in sorted(dict_keys[1:]):
                self.dict_array[key].to_excel(writer,index=False,sheet_name=key)
                logger.debug("save:"+key)
```
使い方
1. このクラスのインスタンスを作成
2. 保存したいDataFrameを`addSheet`メソッドで追加する
    2. `item`：保存するDataFrame
    2. `key` ：保存するシート名。ユニークかつ30文字以内、シート名で使えない文字を除く。
2. すべてのDataFrameを登録したら、`save`メソッドを実行。
    2. `savefpath`：保存するファイルパスを入れる。

```python:main.py
import pandas as pd
import exportXlsx

df1=pd.DataFrame()
df2=pd.DataFrame()
# ~~df1,df2に中身を入れる~~
expXlsx=exportXlsx.exportXlsx()
# 保存するDataFrameの登録
expXlsx.addSheet(df1,"sheetname1")
expXlsx.addSheet(df2,"sheetname2")
# 保存
expXlsx.save("sample.xlsx")
```
## Xlsxに色付け・線引き

DataFrameのIndex,列を指定したセルに色づけをする

```python
def setCellColor(sheet,df,l_idx,colname,fill):
    # 指定されたセルに色を付ける
    # l_idx=df.index
    col_idx=df.columns.tolist().index(colname)
    
    for row_idx in l_idx:
        # print(row_idx,get_column_letter(col_idx))
        # excelでは行は１始まり＆1行目は列名なので+2、列番号も1始まりなので+1
        sheet.cell(row=int(row_idx)+2,column=int(col_idx)+1).fill=fill
    return sheet
    
def setBorderLine(sheet,df,l_idx,border):
    # 指定されたセルに色を付ける
    
    for row_idx in l_idx:
        for col_idx in range(sheet.min_column,sheet.max_column):
            sheet.cell(row_idx+1+1,col_idx).border=border
    return sheet

```

# GPX(xml)を処理し、座標データをDataframeにする
やったこと：StravaのGPXデータから，GoogleMapから取得した座標データと最も近い点を求め，そこを通過した時刻を出力
## 実装概要

- ElementTreeによるxmlデータの読み込み
- 緯度・経度・時刻をDataframeに格納
- Dataframeと指定した緯度・経度を比較し最も近い値の時刻を出力 
### Pythonによるxmlの読み込み
xml.etree.ElementTreeという標準ライブラリを使う．
https://qiita.com/sino20023/items/0314438d397240e56576
https://pg-chain.com/python-xml-elementtree
```python
import xml.etree.ElementTree as ET
tree = ET.parse('_3776_day1_.xml')
root = tree.getroot()
# root[0]:<metadata>，root[1]:<trk>.root[1]の2つ目？を↓で取り出す．
tree_trkseg = root[1][2]
for child in tree_trkseg:
    print(child.attrib['lat'],child.attrib['lon'],child[1].text)
```
このFor文によって、緯度・経度・時刻を抜き出すことができる。
### 必要なデータをDataframeに格納
緯度・経度・時間の３つを格納したDataframeの作成
```python
df_cdata = pd.DataFrame(columns=['lat','lon','time'])

for child in tree_trkseg:
    # lat（緯度）lon（経度）time（時間）をデータフレームに格納
    df_cdata=df_cdata.append(pd.Series([child.attrib['lat'],child.attrib['lon'],child[1].text],index=df_cdata.columns),ignore_index=True)
```
１行目で空のDataframeを作成、２行目のFor文で緯度・経度・時刻の入ったpd.Seriesを作成し、１行目のDataframeにappendします。
### 指定した座標とマッチング
緯度・経度ともにDataframeから最も近い値をそれぞれ探し，その時刻を出力します．
こちらのサイトを参考に、入力した値から一番差が少ないDataframeの行を出力しました。
https://funmatu.wordpress.com/2018/10/01/pandas-dataframe%E5%86%85%E3%81%8B%E3%82%89%E6%9C%80%E3%82%82%E8%BF%91%E3%81%84%E5%80%A4%E3%82%92%E6%8E%A2%E3%81%99/
```python
l_lat = df['lat'].sub(SEARCHLAT).abs().argsort()[:5]
l_lon = df['lon'].sub(SEARCHLON).abs().argsort()[:5]
for i in l_lat:
    print(i,df['time'][i])
print("===============================")
for i in l_lon:
    print(i,df['time'][i])
```

緯度・経度で別々の行が出力されるため，近い順に5行の時刻を出力した後、最も近い（と思われる）値を参考にしました。