【pandas × openpyxl × ダンス】ダンスイベントの事務処理まとめ

 

はじめに

皆さんこんにちは、がんがんです。

実は趣味で昔からダンスをしています。仲間たちと練習会を開いたり、イベントスタッフなどもしています。

イベントを運営するにあたり、エントリー管理や対戦表の作成、タイムスケジュールの作成など多くの事務処理を行う必要があります。

せっかくプログラミングを勉強しているのだから、この事務処理を可能な限り自動化できないかなと昔からずっと考えていました。

 

今年の8月、イベントのスタッフとして事務処理関係を任されました。イベントまで時間があったおかげで、ある程度の処理を自動化することが出来ました。

今回は、自分が忘れないためにもまとめておこうと思います。

 

前提と作成手順

まずは前提として

  •  Pythonを使用する
  •  pandasや前処理の勉強がしたかった

この2つが挙げられます。

私はよく使用する言語がPythonで、当時はDeep Learningの前処理関係をよく調べていました。ちょうどpandasについて勉強したいと思っていたので、今回はpandasを使用して処理を書きました。

 

今回は以下のような手順で作業を進めました。

  1. Googleフォームでエントリーフォームを作成。
  2. Googleスプレッドシートを加工し、csvファイルとしてダウンロードする
  3. pandasを用いてcsvファイルから必要情報を取得、欠落値などの処理
  4. sampleメソッドを用いてランダムサンプリング
  5. openpyxlを用いてエクセル上に情報を格納

前回のイベントでもGoogleフォームは使用していたので、今回新たに追加したのは2~5の工程です。

 

Googleスプレッドシートの加工

Googleスプレッドシートについては何度か使用したことがありますが、加工して使用したのは今回が初めてです。

エントリーフォームの入力は入力者の負担を減らすため、1度の入力で必要な情報をすべて取得する必要があります。そのため、編集者が編集しやすい形式とはなっていません。

今回の場合ですと、エントリーの選択欄に1on1、2on2、Wエントリーの欄が存在しており、これを1on1と2on2の2つのリストに分ける必要があります。

 

そこで、FILTER関数を用いてリストを分けることにしました。

FILTER関数の詳細については以下を参照ください。

Googleスプレッドシート:FILTER関数で条件にあったデータのみを表示させる。 | ひとりで.com

  

FILTER関数を用いて、

・Wエントリーまたは1on1のみ → 1on1のリスト

・Wエントリーまたは2on2のみ → 2on2のリスト
という風に分けることが出来ました。

 

Googleスプレッドシートは使ってみると面白かったので、時間があるときにいろいろ
遊んでみます。以下は参考記事です(この中のrow関数も今回使いました)。
linq.career-tasu.jp

 

pandasによる処理

今回やりたかった勉強のメインです。意外とサクサク進んだので、後述のopenpyxlのがメインっぽいですが。pandasでは以下の3つをやっていきます。

  1. pandasにてcsvファイルの読み込み
  2. NaN値の削除
  3. sampleメソッドを使用してランダムリストの生成

 

まずはpandas部分のコードを以下に示します。


	
	#  csvファイルの読み込み
	df_origin = pd.read_csv(csv_path, engine='python', encoding='utf-8', header=0)
	#  NaN値を削除
	df_origin = df_origin.dropna(thresh=2)
	print("Before List:\n{}".format(df_origin))

	#  sampleメソッドでランダムランプリング
	df_rand = df_origin.sample(frac=1).reset_index(drop=True)
	print("After List:\n{}".format(df_rand))

1.csvファイルの読み込み

csvファイルの読み込みにはread_csvを使用しています。read_csvの使い方についてはこちらを参考にしました。

pandasでcsv/tsvファイル読み込み(read_csv, read_table) | note.nkmk.me

普通にread_csvを使う場合はこちらのみでいいのですが、OSError: Initializing from file failedというエラーと文字化けが起こりました。

 

OSError: Initializing from file failedというエラーはPython3.6の場合に起こるようです。csvファイルの名前を変更すると管理がめんどくさいので、今回はengine='python'として解決しました。

詳しい対策方法はこちらの記事をどうぞ。

Python3.6のpandasで「Initializing from file failed」が起きた場合の対策 | 自調自考の旅

 

文字化けについては、 encoding='utf-8'とすることで解決しました。

 

2.欠損値NaNの除去

こちらを参考にしてコーディングしました。

pandasで欠損値NaNを除外(削除)・置換(穴埋め)・抽出 | note.nkmk.me

 

Googleスプレッドシート上では、更新時に備えてあらかじめ40列用意していました。そのため、エントリー未登録の空白行が多くあります。これが全てNaN値として読み込まれるため、非常に邪魔でした。

手作業で消せば簡単ですが、それだと練習にならないので今回は pandasで消すことにしました。

 

NaNを消すだけであれば、df_origin = df_origin.dropna()で大丈夫です。

ただ、2on2において相方がわからないという場合は相方がNaNとなり、必要なエントリー情報まで消してしまうことになります。そのため、今回は

df_origin = df_origin.dropna(thresh=2)

と記述して、NaN値が2つ以上存在する場合に除去することにしました。

 

3.sampleメソッドを用いてランダムサンプリング

参考記事はこちらになります。

pandas.DataFrame, Seriesの行をランダムソート(シャッフル) | note.nkmk.me

 

前回のイベントではrandom関数により乱数を生成していましたが、それだと非常にめんどうでした。今回はSampleメソッドを用いることで非常に簡単にシャッフルすることが出来ました。

 

Excelによる処理

最後は印刷や管理を効率的に行うためにExcelに格納していきます。今回はpandasよりもopenpyxlの方が苦戦しました。openpyxlで行う処理は大きく分けて2つです。

  1. シートを読み込み、シャッフルなしのリストを格納
  2. シャッフルを行ったリストを格納

 

pandas同様、まずはコードを示します。こちらはソロバトルのコードです。



	"""
		Excelファイルの保存・処理
	"""
	print("-----------------------------------------------------------------")
	print("Open excel file:{}".format(ex_path))
	print("-----------------------------------------------------------------")
	wb = px.load_workbook(ex_path)
	sheetNames = list(wb.get_sheet_names())
	print("{}".format(sheetNames))

	#  Excelファイルに出力(受付用)
	ws = wb[sheetNames[0]]
	wb.active = wb.sheetnames.index(sheetNames[0])
	ws = wb.active				#  シートを取得
	print("0:{}".format(ws))

	for idx,row in enumerate(ws.iter_rows("B3:B33")):
		for cell in row:
			cell.value = df_origin.iloc[idx,1]
	for idx,row in enumerate(ws.iter_rows("C3:C33")):
		for cell in row:
			cell.value = df_origin.iloc[idx,2]


	#  Excelファイルに出力(受付以外)
	for cnt in range(1,4):
		ws = wb[sheetNames[cnt]]
		wb.active = wb.sheetnames.index(sheetNames[cnt])
		ws = wb.active				#  シートを取得
		print("{}:{}".format(cnt,ws))

		for idx,row in enumerate(ws.iter_rows("B3:B33")):
			for cell in row:
				cell.value = df_rand.iloc[idx,1]
		if cnt != 3:
			for idx, row in enumerate(ws.iter_rows("C3:C33")):
				for cell in row:
					cell.value = df_rand.iloc[idx,2]

	#  Excelの保存
	wb.save(ex_path)

受付の人はエントリー順のリスト、DJ・ジャッジ・MCの人はシャッフルしたリストの方がよいかなと思い、このようなコードなっています。

コードを別々に分ければ良かったとまとめていて気付きました。

 

openpyxlの使い方については以下のものがわかりやすく使用させてもらいました。

 Python openpyxlでExcelを操作

OpenPyXLで複数のセルの値を取得する方法

 

Excelの方はどちらも既存のファイルを使用しているため、本プログラムのようになった。

ソロバトルのExcelシートは「受付用、DJ用、ジャッジ用、集計用」、

2on2のExcelシートは「受付・DJ用、ジャッジ用、集計用」のシートをそれぞれ用意しました。

 

まとめ

今回の作成における課題としては

  • トーナメント表への反映が間に合わなかった
  •  Excel処理をもう少し綺麗にコーディングしたかった

この2つが挙げられるかなと思います。トーナメント表に反映させるところは今回断念したので次までの目標にしておきます。

また、GUIでトーナメントを作っても面白いかなと思ったので使う機会はないかもですが気が向いたら作ってみます。