Data Every Day: 学校における生徒の学力

# tldr

Kggleの[Students' Academic Performance Dataset](https://www.kaggle.com/aljarah/xAPI-Edu-Data)を[Predicting Performance in School - Data Every Day #046](https://www.youtube.com/watch?v=ZuoBlgioKO0&list=PLs_CQl4mUvI7Z9HnwoeOS5EUu9gyvekjK&index=46&t=12s)に沿ってやっていきます。

実行環境はGoogle Colaboratorです。

# インポート


```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

import sklearn.preprocessing as sp
from sklearn.model_selection import train_test_split
import sklearn.linear_model as slm

import tensorflow as tf
```

# データのダウンロード

Google Driveをマウントします。


```python
from google.colab import drive
drive.mount('/content/drive')
```

    Mounted at /content/drive


KaggleのAPIクライアントを初期化し、認証します。
認証情報はGoogle Drive内（`/content/drive/My Drive/Colab Notebooks/Kaggle`）に`kaggle.json`として置いてあります。


```python
import os
kaggle_path = "/content/drive/My Drive/Colab Notebooks/Kaggle"
os.environ['KAGGLE_CONFIG_DIR'] = kaggle_path

from kaggle.api.kaggle_api_extended import KaggleApi
api = KaggleApi()
api.authenticate() 
```

Kaggle APIを使ってデータをダウンロードします。


```python
dataset_id = 'aljarah/xAPI-Edu-Data'
dataset = api.dataset_list_files(dataset_id)
file_name = dataset.files[0].name
file_path = os.path.join(api.get_default_download_dir(), file_name)
file_path
```




    '/content/xAPI-Edu-Data.csv'




```python
api.dataset_download_file(dataset_id, file_name, force=True, quiet=False)
```

    100%|██████████| 37.1k/37.1k [00:00<00:00, 19.1MB/s]

    Downloading xAPI-Edu-Data.csv to /content
    


    





    True



# データの読み込み


```python
data = pd.read_csv(file_path)
```


```python
data
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>gender</th>
      <th>NationalITy</th>
      <th>PlaceofBirth</th>
      <th>StageID</th>
      <th>GradeID</th>
      <th>SectionID</th>
      <th>Topic</th>
      <th>Semester</th>
      <th>Relation</th>
      <th>raisedhands</th>
      <th>VisITedResources</th>
      <th>AnnouncementsView</th>
      <th>Discussion</th>
      <th>ParentAnsweringSurvey</th>
      <th>ParentschoolSatisfaction</th>
      <th>StudentAbsenceDays</th>
      <th>Class</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>M</td>
      <td>KW</td>
      <td>KuwaIT</td>
      <td>lowerlevel</td>
      <td>G-04</td>
      <td>A</td>
      <td>IT</td>
      <td>F</td>
      <td>Father</td>
      <td>15</td>
      <td>16</td>
      <td>2</td>
      <td>20</td>
      <td>Yes</td>
      <td>Good</td>
      <td>Under-7</td>
      <td>M</td>
    </tr>
    <tr>
      <th>1</th>
      <td>M</td>
      <td>KW</td>
      <td>KuwaIT</td>
      <td>lowerlevel</td>
      <td>G-04</td>
      <td>A</td>
      <td>IT</td>
      <td>F</td>
      <td>Father</td>
      <td>20</td>
      <td>20</td>
      <td>3</td>
      <td>25</td>
      <td>Yes</td>
      <td>Good</td>
      <td>Under-7</td>
      <td>M</td>
    </tr>
    <tr>
      <th>2</th>
      <td>M</td>
      <td>KW</td>
      <td>KuwaIT</td>
      <td>lowerlevel</td>
      <td>G-04</td>
      <td>A</td>
      <td>IT</td>
      <td>F</td>
      <td>Father</td>
      <td>10</td>
      <td>7</td>
      <td>0</td>
      <td>30</td>
      <td>No</td>
      <td>Bad</td>
      <td>Above-7</td>
      <td>L</td>
    </tr>
    <tr>
      <th>3</th>
      <td>M</td>
      <td>KW</td>
      <td>KuwaIT</td>
      <td>lowerlevel</td>
      <td>G-04</td>
      <td>A</td>
      <td>IT</td>
      <td>F</td>
      <td>Father</td>
      <td>30</td>
      <td>25</td>
      <td>5</td>
      <td>35</td>
      <td>No</td>
      <td>Bad</td>
      <td>Above-7</td>
      <td>L</td>
    </tr>
    <tr>
      <th>4</th>
      <td>M</td>
      <td>KW</td>
      <td>KuwaIT</td>
      <td>lowerlevel</td>
      <td>G-04</td>
      <td>A</td>
      <td>IT</td>
      <td>F</td>
      <td>Father</td>
      <td>40</td>
      <td>50</td>
      <td>12</td>
      <td>50</td>
      <td>No</td>
      <td>Bad</td>
      <td>Above-7</td>
      <td>M</td>
    </tr>
    <tr>
      <th>...</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>
    </tr>
    <tr>
      <th>475</th>
      <td>F</td>
      <td>Jordan</td>
      <td>Jordan</td>
      <td>MiddleSchool</td>
      <td>G-08</td>
      <td>A</td>
      <td>Chemistry</td>
      <td>S</td>
      <td>Father</td>
      <td>5</td>
      <td>4</td>
      <td>5</td>
      <td>8</td>
      <td>No</td>
      <td>Bad</td>
      <td>Above-7</td>
      <td>L</td>
    </tr>
    <tr>
      <th>476</th>
      <td>F</td>
      <td>Jordan</td>
      <td>Jordan</td>
      <td>MiddleSchool</td>
      <td>G-08</td>
      <td>A</td>
      <td>Geology</td>
      <td>F</td>
      <td>Father</td>
      <td>50</td>
      <td>77</td>
      <td>14</td>
      <td>28</td>
      <td>No</td>
      <td>Bad</td>
      <td>Under-7</td>
      <td>M</td>
    </tr>
    <tr>
      <th>477</th>
      <td>F</td>
      <td>Jordan</td>
      <td>Jordan</td>
      <td>MiddleSchool</td>
      <td>G-08</td>
      <td>A</td>
      <td>Geology</td>
      <td>S</td>
      <td>Father</td>
      <td>55</td>
      <td>74</td>
      <td>25</td>
      <td>29</td>
      <td>No</td>
      <td>Bad</td>
      <td>Under-7</td>
      <td>M</td>
    </tr>
    <tr>
      <th>478</th>
      <td>F</td>
      <td>Jordan</td>
      <td>Jordan</td>
      <td>MiddleSchool</td>
      <td>G-08</td>
      <td>A</td>
      <td>History</td>
      <td>F</td>
      <td>Father</td>
      <td>30</td>
      <td>17</td>
      <td>14</td>
      <td>57</td>
      <td>No</td>
      <td>Bad</td>
      <td>Above-7</td>
      <td>L</td>
    </tr>
    <tr>
      <th>479</th>
      <td>F</td>
      <td>Jordan</td>
      <td>Jordan</td>
      <td>MiddleSchool</td>
      <td>G-08</td>
      <td>A</td>
      <td>History</td>
      <td>S</td>
      <td>Father</td>
      <td>35</td>
      <td>14</td>
      <td>23</td>
      <td>62</td>
      <td>No</td>
      <td>Bad</td>
      <td>Above-7</td>
      <td>L</td>
    </tr>
  </tbody>
</table>
<p>480 rows × 17 columns</p>
</div>



# 下準備


```python
data.info()
```

    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 480 entries, 0 to 479
    Data columns (total 17 columns):
     #   Column                    Non-Null Count  Dtype 
    ---  ------                    --------------  ----- 
     0   gender                    480 non-null    object
     1   NationalITy               480 non-null    object
     2   PlaceofBirth              480 non-null    object
     3   StageID                   480 non-null    object
     4   GradeID                   480 non-null    object
     5   SectionID                 480 non-null    object
     6   Topic                     480 non-null    object
     7   Semester                  480 non-null    object
     8   Relation                  480 non-null    object
     9   raisedhands               480 non-null    int64 
     10  VisITedResources          480 non-null    int64 
     11  AnnouncementsView         480 non-null    int64 
     12  Discussion                480 non-null    int64 
     13  ParentAnsweringSurvey     480 non-null    object
     14  ParentschoolSatisfaction  480 non-null    object
     15  StudentAbsenceDays        480 non-null    object
     16  Class                     480 non-null    object
    dtypes: int64(4), object(13)
    memory usage: 63.9+ KB



```python
def get_uniques(df, columns):
    return {column: list(df[column].unique()) for column in columns}

def get_categorical_columns(df):
    return [column for column in df.columns if df.dtypes[column] == 'object']
```


```python
get_uniques(data, get_categorical_columns(data))
```




    {'Class': ['M', 'L', 'H'],
     'GradeID': ['G-04',
      'G-07',
      'G-08',
      'G-06',
      'G-05',
      'G-09',
      'G-12',
      'G-11',
      'G-10',
      'G-02'],
     'NationalITy': ['KW',
      'lebanon',
      'Egypt',
      'SaudiArabia',
      'USA',
      'Jordan',
      'venzuela',
      'Iran',
      'Tunis',
      'Morocco',
      'Syria',
      'Palestine',
      'Iraq',
      'Lybia'],
     'ParentAnsweringSurvey': ['Yes', 'No'],
     'ParentschoolSatisfaction': ['Good', 'Bad'],
     'PlaceofBirth': ['KuwaIT',
      'lebanon',
      'Egypt',
      'SaudiArabia',
      'USA',
      'Jordan',
      'venzuela',
      'Iran',
      'Tunis',
      'Morocco',
      'Syria',
      'Iraq',
      'Palestine',
      'Lybia'],
     'Relation': ['Father', 'Mum'],
     'SectionID': ['A', 'B', 'C'],
     'Semester': ['F', 'S'],
     'StageID': ['lowerlevel', 'MiddleSchool', 'HighSchool'],
     'StudentAbsenceDays': ['Under-7', 'Above-7'],
     'Topic': ['IT',
      'Math',
      'Arabic',
      'Science',
      'English',
      'Quran',
      'Spanish',
      'French',
      'History',
      'Biology',
      'Chemistry',
      'Geology'],
     'gender': ['M', 'F']}




```python
binary_feayures = ['gender', 'Semester', 'Relation', 'ParentAnsweringSurvey', 'ParentschoolSatisfaction', 'StudentAbsenceDays']
ordianl_features = ['StageID', 'GradeID']
nominal_features = ['NationalITy', 'PlaceofBirth', 'SectionID', 'Topic']

target_column = 'Class'
```


```python
binary_positive = ['M', 'S', 'Father', 'Yes', 'Good', 'Above-7']
stage_ordering = ['lowerlevel', 'MiddleSchool', 'HighSchool']
grade_ordering = [
    'G-02',
    'G-04',
    'G-05',
    'G-06',
    'G-07',
    'G-08',
    'G-09',
    'G-10',
    'G-11',
    'G-12',
]

nominal_prefixes = ['N', 'B', 'S', 'T']
```


```python
def binary_encode(df, column, positive_value):
    df = df.copy()
    df[column] = df[column].apply(lambda x: 1 if x == positive_value else 0)
    return df

def ordinal_encode(df, column, ordering):
    df = df.copy()
    df[column] = df[column].apply(lambda x: ordering.index(x))
    return df

def onehot_encode(df, column, prefix):
    df = df.copy()
    dummies = pd.get_dummies(df[column], prefix=prefix)
    df = pd.concat([df, dummies], axis=1)
    df = df.drop(column, axis=1)
    return df
```


```python
for feature, positive_value in zip(binary_feayures, binary_positive):
    data = binary_encode(data, feature, positive_value)
```


```python
data = ordinal_encode(data, 'StageID', stage_ordering)
data = ordinal_encode(data, 'GradeID', grade_ordering)
```


```python
for feature, prefix in zip(nominal_features, nominal_prefixes):
    data = onehot_encode(data, feature, prefix)
```


```python
target_ordering = ['L', 'M', 'H']
data = ordinal_encode(data, target_column, target_ordering)
```

# 分割とスケーリング


```python
y = data[target_column]
X = data.drop([target_column], axis=1)
```


```python
scaler = sp.StandardScaler()
X = scaler.fit_transform(X)
```


```python
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.8)
```

# トレーニング


```python
X.shape
```




    (480, 55)




```python
model = tf.keras.Sequential([
    tf.keras.layers.Dense(64, activation='relu', input_shape=(55,)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(3, activation='softmax'),
])

model.summary()

model.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy'],
)

batch_size = 64
epochs = 100

history = model.fit(
    X_train,
    y_train,
    validation_split=0.2,
    batch_size=batch_size,
    epochs=epochs,
    verbose=0,
)
```

    Model: "sequential_4"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    dense_12 (Dense)             (None, 64)                3584      
    _________________________________________________________________
    dense_13 (Dense)             (None, 64)                4160      
    _________________________________________________________________
    dense_14 (Dense)             (None, 3)                 195       
    =================================================================
    Total params: 7,939
    Trainable params: 7,939
    Non-trainable params: 0
    _________________________________________________________________



```python
plt.figure(figsize=(14, 10))

epochs_range = range(1, epochs+1)
train_loss = history.history['loss']
val_loss = history.history['val_loss']

plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')

plt.title('Training and Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()

plt.show()
```


![Students'_Academic_Performance_Dataset_31_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141539/f3b35c84-8485-5ec3-8dcb-5637d0ae6379.png)



```python
np.argmin(val_loss)
```




    25