ITパスポート試験の過去問の "正解選択肢だけ" を分析するためのデータ処理


## この記事の内容

ネタです。  
ITパスポートの4択ア、イ、ウ、エのパターンを分析するために、データを構造化してみます。  
過去問対策には全くならないので、あまり間に受けないようご注意ください。

## 処理の流れ

1. PDFファイルの取得
1. PDFからテキストの読み取り
1. データの整理
1. 簡単な分析例

## 環境

* macOS Monterey 12.6
* Python 3.8.5
    * beautiful-soup
    * pandas
    * pdfminer
    * matplotlib
        * データ整理までなら不要
    * statsmodels
        * 同上

## PDFファイルの取得

ITパスポート試験の過去問ページでPDFが公開されているので、`BeaufulSoup`でダウンロードURLを一括で拾ってローカルにダウンロードします。

```python
# 使うモジュール
import requests
from urllib.request import urlopen
from urllib.parse import urljoin
import time
import os
from bs4 import BeautifulSoup
```

```python
# URL一覧の取得
base_url = "https://www3.jitec.ipa.go.jp/JitesCbt/html/openinfo/questions.html"
res = urlopen(base_url)
soup = BeautifulSoup(res.read())
tar_table = soup.find_all("table")[1]
titles = tar_table.find_all("td", attrs={"class":"table_10_1"})
titles = [t.text.strip() for t in titles]
answers = tar_table.find_all("td", attrs={"class":"table_10_4"})
urls = [a.find("a").get("href") for a in answers]
urls = [urljoin(base_url,u) for u in urls]
title_url_dict = dict(zip(titles,urls))
title_url_dict
```

こんな感じの辞書ができます。

```
{'ITパスポート試験 令和4年度分': 'https://www3.jitec.ipa.go.jp/JitesCbt/html/openinfo/pdf/questions/2022r04_ip_ans.pdf',
 'ITパスポート試験 令和3年度分': 'https://www3.jitec.ipa.go.jp/JitesCbt/html/openinfo/pdf/questions/2021r03_ip_ans.pdf',
(中略)
'ITパスポート試験 平成21年度 春期分': 'https://www3.jitec.ipa.go.jp/JitesCbt/html/openinfo/pdf/questions/2009h21h_ip_ans.pdf'}
```

あとは順番にダウンロードすれば良いです。サーバーに負荷をかけないように、待機時間を入れるのを忘れずに。

```python
# PDFのダウンロード
dirname = "pdf"
os.makedirs(dirname,exist_ok=True)
for title, url in title_url_dict.items():
    print(f"downloading {title}")
    pdf = urlopen(url)
    with open(os.path.join(dirname,title+".pdf"), "wb") as f:
        f.write(pdf.read())
    time.sleep(3)
    
print("done.")
```

`done.` が表示されたら終了です。

```
downloading ITパスポート試験 令和4年度分
downloading ITパスポート試験 令和3年度分
(中略)
downloading ITパスポート試験 平成21年度 春期分
done.
```

## PDFからテキストの読み取り

ダウンロードしたPDFファイルから、`pdfminer`を使って文字を読み取ります。  
`pdfminer` は表組みになっているものは縦方向に順番に文字を読み取るようなので、ITパスポートの様式にぴったりですね。ラッキー。  
結果のリストは各試験ごとに辞書型で格納します。

```python
# モジュールの読み込み
from pdfminer.high_level import extract_text
import re
```

```python
# PDFからテキストを読み取る
# 正規表現で「ア」から「エ」までの文字を抜き取る
table_data = {}
pat = re.compile(r"[ア-エ]")
for title in titles:
    path = os.path.join(dirname, title+".pdf")
    text = extract_text(path)
    seq = pat.findall(text)
    ver = title.split(" ", 1)[1]
    table_data[ver] = seq
```

## データの整理

あとは`pandas`データフレームに入れてあげるだけです。  
1点注意が必要なのが、平成21年の秋試験は、多答問題があるため回答数が揃っていません。  
今回は泣く泣く除外するとします。

```python
# モジュールのインポート
import pandas as pd
```

```python
# 各試験での回答数を表示
for k,v in table_data.items():
    print(k, len(v))
```

```
令和4年度分 100
令和3年度分 100
(中略)
平成22年度 春期分 100
平成21年度 秋期分 102
平成21年度 春期分 100
```

辞書を`pd.DataFrame`に放り込むと簡単にデータフレームにしてくれます。

```python
# データフレーム型に変換
table_data = {k:v for k,v in table_data.items() if len(v) == 100}
df = pd.DataFrame(table_data)
df.head()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>令和4年度分</th>
      <th>令和3年度分</th>
      <th>令和2年度 10月分</th>
      <th>令和元年度 秋期分</th>
      <th>平成31年度 春期分</th>
      <th>平成30年度 秋期分</th>
      <th>平成30年度 春期分</th>
      <th>平成29年度 秋期分</th>
      <th>平成29年度 春期分</th>
      <th>平成28年度 秋期分</th>
      <th>平成28年度 春期分</th>
      <th>平成27年度 秋期分</th>
      <th>平成27年度 春期分</th>
      <th>平成26年度 秋期分</th>
      <th>平成26年度 春期分</th>
      <th>平成25年度 秋期分</th>
      <th>平成25年度 春期分</th>
      <th>平成24年度 秋期分</th>
      <th>平成24年度 春期分</th>
      <th>平成23年度 秋期分</th>
      <th>平成23年度 特別分</th>
      <th>平成22年度 秋期分</th>
      <th>平成22年度 春期分</th>
      <th>平成21年度 春期分</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>エ</td>
      <td>ウ</td>
      <td>ウ</td>
      <td>エ</td>
      <td>エ</td>
      <td>ア</td>
      <td>エ</td>
      <td>ウ</td>
      <td>ア</td>
      <td>エ</td>
      <td>ア</td>
      <td>ア</td>
      <td>ウ</td>
      <td>ウ</td>
      <td>ア</td>
      <td>エ</td>
      <td>ア</td>
      <td>エ</td>
      <td>イ</td>
      <td>ア</td>
      <td>ア</td>
      <td>エ</td>
      <td>ア</td>
      <td>ウ</td>
    </tr>
    <tr>
      <th>1</th>
      <td>イ</td>
      <td>エ</td>
      <td>イ</td>
      <td>ア</td>
      <td>ウ</td>
      <td>ウ</td>
      <td>ウ</td>
      <td>エ</td>
      <td>イ</td>
      <td>ア</td>
      <td>ア</td>
      <td>ア</td>
      <td>ア</td>
      <td>ウ</td>
      <td>エ</td>
      <td>ア</td>
      <td>ウ</td>
      <td>イ</td>
      <td>ア</td>
      <td>イ</td>
      <td>イ</td>
      <td>ア</td>
      <td>イ</td>
      <td>ウ</td>
    </tr>
    <tr>
      <th>2</th>
      <td>ウ</td>
      <td>ウ</td>
      <td>イ</td>
      <td>ウ</td>
      <td>ア</td>
      <td>ウ</td>
      <td>イ</td>
      <td>ア</td>
      <td>ア</td>
      <td>ウ</td>
      <td>エ</td>
      <td>エ</td>
      <td>エ</td>
      <td>ア</td>
      <td>エ</td>
      <td>エ</td>
      <td>イ</td>
      <td>ア</td>
      <td>イ</td>
      <td>エ</td>
      <td>イ</td>
      <td>イ</td>
      <td>エ</td>
      <td>イ</td>
    </tr>
    <tr>
      <th>3</th>
      <td>ア</td>
      <td>ア</td>
      <td>ウ</td>
      <td>エ</td>
      <td>イ</td>
      <td>ウ</td>
      <td>ア</td>
      <td>イ</td>
      <td>イ</td>
      <td>エ</td>
      <td>ウ</td>
      <td>ア</td>
      <td>ウ</td>
      <td>イ</td>
      <td>イ</td>
      <td>ウ</td>
      <td>ア</td>
      <td>イ</td>
      <td>ア</td>
      <td>エ</td>
      <td>イ</td>
      <td>ア</td>
      <td>ウ</td>
      <td>ア</td>
    </tr>
    <tr>
      <th>4</th>
      <td>イ</td>
      <td>ウ</td>
      <td>イ</td>
      <td>エ</td>
      <td>ア</td>
      <td>ウ</td>
      <td>イ</td>
      <td>ア</td>
      <td>イ</td>
      <td>イ</td>
      <td>ウ</td>
      <td>ア</td>
      <td>ア</td>
      <td>ウ</td>
      <td>エ</td>
      <td>ウ</td>
      <td>エ</td>
      <td>イ</td>
      <td>ウ</td>
      <td>イ</td>
      <td>ウ</td>
      <td>ウ</td>
      <td>イ</td>
      <td>イ</td>
    </tr>
  </tbody>
</table>

数値として分析したい人のために、ア→0、イ→1 みたいな変換をしたデータも作っておきましょう。

```python
# 変数の変換
df_num = df.replace({
    "ア":0,
    "イ":1,
    "ウ":2,
    "エ":3
})
df_num.head()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>令和4年度分</th>
      <th>令和3年度分</th>
      <th>令和2年度 10月分</th>
      <th>令和元年度 秋期分</th>
      <th>平成31年度 春期分</th>
      <th>平成30年度 秋期分</th>
      <th>平成30年度 春期分</th>
      <th>平成29年度 秋期分</th>
      <th>平成29年度 春期分</th>
      <th>平成28年度 秋期分</th>
      <th>平成28年度 春期分</th>
      <th>平成27年度 秋期分</th>
      <th>平成27年度 春期分</th>
      <th>平成26年度 秋期分</th>
      <th>平成26年度 春期分</th>
      <th>平成25年度 秋期分</th>
      <th>平成25年度 春期分</th>
      <th>平成24年度 秋期分</th>
      <th>平成24年度 春期分</th>
      <th>平成23年度 秋期分</th>
      <th>平成23年度 特別分</th>
      <th>平成22年度 秋期分</th>
      <th>平成22年度 春期分</th>
      <th>平成21年度 春期分</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>3</td>
      <td>2</td>
      <td>2</td>
      <td>3</td>
      <td>3</td>
      <td>0</td>
      <td>3</td>
      <td>2</td>
      <td>0</td>
      <td>3</td>
      <td>0</td>
      <td>0</td>
      <td>2</td>
      <td>2</td>
      <td>0</td>
      <td>3</td>
      <td>0</td>
      <td>3</td>
      <td>1</td>
      <td>0</td>
      <td>0</td>
      <td>3</td>
      <td>0</td>
      <td>2</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1</td>
      <td>3</td>
      <td>1</td>
      <td>0</td>
      <td>2</td>
      <td>2</td>
      <td>2</td>
      <td>3</td>
      <td>1</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>2</td>
      <td>3</td>
      <td>0</td>
      <td>2</td>
      <td>1</td>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td>0</td>
      <td>1</td>
      <td>2</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>2</td>
      <td>1</td>
      <td>2</td>
      <td>0</td>
      <td>2</td>
      <td>1</td>
      <td>0</td>
      <td>0</td>
      <td>2</td>
      <td>3</td>
      <td>3</td>
      <td>3</td>
      <td>0</td>
      <td>3</td>
      <td>3</td>
      <td>1</td>
      <td>0</td>
      <td>1</td>
      <td>3</td>
      <td>1</td>
      <td>1</td>
      <td>3</td>
      <td>1</td>
    </tr>
    <tr>
      <th>3</th>
      <td>0</td>
      <td>0</td>
      <td>2</td>
      <td>3</td>
      <td>1</td>
      <td>2</td>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td>3</td>
      <td>2</td>
      <td>0</td>
      <td>2</td>
      <td>1</td>
      <td>1</td>
      <td>2</td>
      <td>0</td>
      <td>1</td>
      <td>0</td>
      <td>3</td>
      <td>1</td>
      <td>0</td>
      <td>2</td>
      <td>0</td>
    </tr>
    <tr>
      <th>4</th>
      <td>1</td>
      <td>2</td>
      <td>1</td>
      <td>3</td>
      <td>0</td>
      <td>2</td>
      <td>1</td>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td>2</td>
      <td>0</td>
      <td>0</td>
      <td>2</td>
      <td>3</td>
      <td>2</td>
      <td>3</td>
      <td>1</td>
      <td>2</td>
      <td>1</td>
      <td>2</td>
      <td>2</td>
      <td>1</td>
      <td>1</td>
    </tr>
  </tbody>
</table>

## 簡単な分析例

後はよしなに、好き勝手分析してみてください。

例えば毎年の正答選択肢の数を集計するならこんな感じ。

```python
# データを縦持ちにする
df_long = df.unstack()
df_long.name = "value"
df_long = df_long.reset_index()
# グループ集計
df_long.groupby("level_0")["value"].value_counts().unstack().loc[df.columns]
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th>value</th>
      <th>ア</th>
      <th>イ</th>
      <th>ウ</th>
      <th>エ</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>令和4年度分</th>
      <td>32</td>
      <td>27</td>
      <td>16</td>
      <td>25</td>
    </tr>
    <tr>
      <th>令和3年度分</th>
      <td>29</td>
      <td>20</td>
      <td>25</td>
      <td>26</td>
    </tr>
    <tr>
      <th>令和2年度 10月分</th>
      <td>27</td>
      <td>25</td>
      <td>23</td>
      <td>25</td>
    </tr>
    <tr>
      <th>令和元年度 秋期分</th>
      <td>21</td>
      <td>20</td>
      <td>23</td>
      <td>36</td>
    </tr>
    <tr>
      <th>平成31年度 春期分</th>
      <td>26</td>
      <td>22</td>
      <td>26</td>
      <td>26</td>
    </tr>
    <tr>
      <th>平成30年度 秋期分</th>
      <td>19</td>
      <td>25</td>
      <td>28</td>
      <td>28</td>
    </tr>
    <tr>
      <th>平成30年度 春期分</th>
      <td>30</td>
      <td>21</td>
      <td>27</td>
      <td>22</td>
    </tr>
    <tr>
      <th>平成29年度 秋期分</th>
      <td>18</td>
      <td>29</td>
      <td>31</td>
      <td>22</td>
    </tr>
    <tr>
      <th>平成29年度 春期分</th>
      <td>14</td>
      <td>25</td>
      <td>22</td>
      <td>39</td>
    </tr>
    <tr>
      <th>平成28年度 秋期分</th>
      <td>27</td>
      <td>27</td>
      <td>20</td>
      <td>26</td>
    </tr>
    <tr>
      <th>平成28年度 春期分</th>
      <td>25</td>
      <td>21</td>
      <td>32</td>
      <td>22</td>
    </tr>
    <tr>
      <th>平成27年度 秋期分</th>
      <td>30</td>
      <td>23</td>
      <td>29</td>
      <td>18</td>
    </tr>
    <tr>
      <th>平成27年度 春期分</th>
      <td>19</td>
      <td>23</td>
      <td>31</td>
      <td>27</td>
    </tr>
    <tr>
      <th>平成26年度 秋期分</th>
      <td>21</td>
      <td>25</td>
      <td>34</td>
      <td>20</td>
    </tr>
    <tr>
      <th>平成26年度 春期分</th>
      <td>22</td>
      <td>24</td>
      <td>26</td>
      <td>28</td>
    </tr>
    <tr>
      <th>平成25年度 秋期分</th>
      <td>20</td>
      <td>26</td>
      <td>26</td>
      <td>28</td>
    </tr>
    <tr>
      <th>平成25年度 春期分</th>
      <td>23</td>
      <td>28</td>
      <td>23</td>
      <td>26</td>
    </tr>
    <tr>
      <th>平成24年度 秋期分</th>
      <td>18</td>
      <td>28</td>
      <td>31</td>
      <td>23</td>
    </tr>
    <tr>
      <th>平成24年度 春期分</th>
      <td>25</td>
      <td>24</td>
      <td>30</td>
      <td>21</td>
    </tr>
    <tr>
      <th>平成23年度 秋期分</th>
      <td>17</td>
      <td>29</td>
      <td>37</td>
      <td>17</td>
    </tr>
    <tr>
      <th>平成23年度 特別分</th>
      <td>23</td>
      <td>30</td>
      <td>26</td>
      <td>21</td>
    </tr>
    <tr>
      <th>平成22年度 秋期分</th>
      <td>22</td>
      <td>34</td>
      <td>20</td>
      <td>24</td>
    </tr>
    <tr>
      <th>平成22年度 春期分</th>
      <td>18</td>
      <td>29</td>
      <td>30</td>
      <td>23</td>
    </tr>
    <tr>
      <th>平成21年度 春期分</th>
      <td>23</td>
      <td>30</td>
      <td>31</td>
      <td>16</td>
    </tr>
  </tbody>
</table>

自己相関関数を書いてみたりもできます。

```python
# モジュールのインポート
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import acf
plt.rcParams['font.sans-serif'] = ['Hiragino Maru Gothic Pro', 'Yu Gothic']
%matplotlib inline

# 全ての列に自己相関関数を適用して折れ線グラフで表示
fig = plt.figure(figsize=(10,12))
ax = fig.add_subplot()
df_num.apply(lambda x:acf(x), axis=0).plot(ax=ax)
plt.show()
```

![acf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315348/bcaaf130-7e08-7134-d4f1-f7b51861066a.png)

## おわり

ITパスポートを受けようとする人はこんな事をやっていないで、過去問をひたすら解いたら良いんじゃないでしょうか。

## 参考にしたもの

* https://gammasoft.jp/blog/python-parse-pdf-contents/
* https://pypi.org/project/pdfminer.six/
* https://qiita.com/checkpoint/items/d9bcc63292d7f01c62d3
* https://pystyle.info/scraping-tutorials-for-static-website/#outline__2_3_2
* https://teratail.com/questions/27753