学習者言語の分析(基礎)1(第4回)

  • 4.1 Pandasとは
  • 4.2 データの抽出
  • 4.3 Pandasの基本操作
  • 4.4 Pandasを用いたデータの可視化
  • 4.5 データの読み込みと追加
  • 4.6 カテゴリごとの統計量
  • 練習問題
  • 前回の授業では学習者が書いた作文のファイルを読み込み、個々の学習者ごとに1文あたりの平均語数、タイプ・トークン比をカウントしました。
  • このように学習者が産出した言語から得られる数量的な情報を特徴量と呼び、特徴量を得ることは特徴量抽出と言います。
  • 今回の授業ではこれらの特徴量をPandasというパッケージを用いて整理し、初歩的な統計量を算出し、可視化します。

4.1 Pandasとは¶

  • Pythonでデータ分析に使用されるパッケージ。
  • Rのdataframeのようなもの。
  • PandasにはSeriesとDataFrameというデータ構造がありますが、
  • 基本的にDataFrameの扱い方を学びます。

4.2 データの抽出¶

In [1]:
import os
# ファイル名の抽出
F = os.listdir("../DATA01/text01/")
F.sort()
# 作文ファイルの読み込み
# Tはひとつの要素が学習者ひとりひとりが書いた作文

T = []

for f in F:
    t = open("../DATA01/text01/"+f,'r')
    text = t.read()
    T.append(text)
  • 文数、単語数、文字数をカウントします。
In [2]:
# sent_tokenize、word_tokenizeのimport
from nltk import sent_tokenize, word_tokenize

# 文の数を数える
SENT = []

for t in T:
    SENT.append(len(sent_tokenize(t)))

# 単語の数を数える
WORD = []

for t in T:
    WORD.append(len(word_tokenize(t)))

# 文字数を数える
LETTER = []

for t in T:
    s = "".join(t)
    LETTER.append(len(s))
  • Fにファイル名、SENTに文の数、WORDに語数、LETTERに文字数が保存されている。
  • 表などを用いて全体の傾向などを把握したい。

4.3 Pandasの基本操作¶

  • 上で抽出した特徴量を表にまとめる。
In [3]:
# pandasのimport
import pandas as pd

# データフレームの作成
# 「"列名":データを含むリスト」をひとつのセットして書く
# "index"は行名。ここではファイル名が保存してあるFを指定した
data = pd.DataFrame({"num_of_sents":SENT,
                                        "num_of_words":WORD,
                                        "num_of_letter":LETTER},
                                       index=F)
In [4]:
# DataFrameの中身の確認
# 最初の5行が表示される
data.head()
Out[4]:
num_of_sents num_of_words num_of_letter
JPN002.txt 25 392 1969
JPN004.txt 18 288 1555
JPN006.txt 26 548 3042
JPN008.txt 21 332 1747
JPN010.txt 18 391 1987
  • Pandasでは横がindex(行)、縦がcolumn(列)と呼ばれる。
  • 上の"JPN002.txt"は0行目、"num_of_sents"は1列目
  • 以下ではデータへのアクセス方法を学ぶ。
In [5]:
# 列へのアクセス
data["num_of_letter"]
Out[5]:
JPN002.txt    1969
JPN004.txt    1555
JPN006.txt    3042
JPN008.txt    1747
JPN010.txt    1987
JPN011.txt    2331
JPN012.txt    1160
JPN015.txt    1990
JPN022.txt    1466
JPN038.txt    1415
JPN053.txt    2328
JPN060.txt    3080
JPN061.txt    1252
JPN062.txt    1852
JPN078.txt    4893
JPN082.txt    2891
JPN086.txt    1295
JPN090.txt    2249
JPN092.txt    2306
JPN093.txt    1417
JPN095.txt    1790
JPN117.txt    2000
JPN120.txt    1357
JPN122.txt    2757
JPN126.txt    1958
JPN132.txt    2124
JPN135.txt    2115
JPN137.txt    1299
JPN139.txt    1816
JPN142.txt    2789
JPN145.txt    1569
JPN147.txt    2499
JPN148.txt    1606
JPN155.txt    1988
JPN156.txt    3162
JPN157.txt    3975
JPN159.txt    2828
JPN163.txt    2574
JPN164.txt    2530
JPN166.txt    2277
JPN168.txt    2277
JPN169.txt    2214
JPN173.txt    2374
JPN176.txt    1359
JPN179.txt    1388
JPN186.txt    1560
JPN189.txt    2619
JPN191.txt    1794
JPN198.txt    1438
Name: num_of_letter, dtype: int64
In [6]:
# 行へのアクセス
data.loc["JPN002.txt"]
Out[6]:
num_of_sents       25
num_of_words      392
num_of_letter    1969
Name: JPN002.txt, dtype: int64
In [7]:
# 列の平均
data.mean()
Out[7]:
num_of_sents       25.857143
num_of_words      419.877551
num_of_letter    2127.775510
dtype: float64
In [8]:
# 条件指定: 500語より多くの語が含まれている作文
data[data["num_of_words"]>500]
Out[8]:
num_of_sents num_of_words num_of_letter
JPN006.txt 26 548 3042
JPN060.txt 53 674 3080
JPN078.txt 64 1013 4893
JPN082.txt 24 584 2891
JPN092.txt 27 502 2306
JPN142.txt 33 573 2789
JPN156.txt 30 566 3162
JPN157.txt 33 702 3975
JPN159.txt 27 528 2828
JPN164.txt 29 536 2530
In [9]:
# ソート: 文の数が少ない順
# by = ""で列名を指定
data.sort_values(by="num_of_sents")
Out[9]:
num_of_sents num_of_words num_of_letter
JPN198.txt 10 249 1438
JPN179.txt 13 246 1388
JPN189.txt 15 465 2619
JPN186.txt 15 311 1560
JPN137.txt 16 240 1299
JPN011.txt 18 462 2331
JPN004.txt 18 288 1555
JPN010.txt 18 391 1987
JPN012.txt 19 230 1160
JPN145.txt 19 314 1569
JPN061.txt 19 264 1252
JPN139.txt 19 325 1816
JPN120.txt 19 272 1357
JPN148.txt 20 304 1606
JPN163.txt 20 448 2574
JPN008.txt 21 332 1747
JPN022.txt 21 302 1466
JPN176.txt 21 280 1359
JPN169.txt 22 452 2214
JPN062.txt 23 369 1852
JPN168.txt 23 458 2277
JPN155.txt 24 381 1988
JPN086.txt 24 292 1295
JPN038.txt 24 285 1415
JPN082.txt 24 584 2891
JPN173.txt 25 478 2374
JPN166.txt 25 460 2277
JPN002.txt 25 392 1969
JPN122.txt 25 499 2757
JPN015.txt 25 406 1990
JPN006.txt 26 548 3042
JPN090.txt 26 458 2249
JPN126.txt 27 388 1958
JPN191.txt 27 355 1794
JPN092.txt 27 502 2306
JPN159.txt 27 528 2828
JPN093.txt 27 286 1417
JPN147.txt 28 477 2499
JPN164.txt 29 536 2530
JPN156.txt 30 566 3162
JPN053.txt 32 472 2328
JPN157.txt 33 702 3975
JPN142.txt 33 573 2789
JPN135.txt 36 441 2115
JPN095.txt 41 430 1790
JPN132.txt 45 440 2124
JPN117.txt 46 406 2000
JPN060.txt 53 674 3080
JPN078.txt 64 1013 4893
In [10]:
# 表示する小数点以下の桁数を設定
pd.options.display.precision = 2

# 基本統計量
data.describe()

# count -> データの数
# mean -> 平均値
# std -> 標準偏差(個々のデータが平均からどれだけばらついているかの指標)
# min -> 最小値
# 25% -> 第一四分位数(データを小さい方から並べて全体の25%のところにあるデータ)
# 50% -> 中央値(データを小さい方から並べて全体の50%のところにあるデータ)
# 75% -> 第三四分位数(データを小さい方から並べて全体の75%のところにあるデータ)
# max -> 最大値
Out[10]:
num_of_sents num_of_words num_of_letter
count 49.00 49.00 49.00
mean 25.86 419.88 2127.78
std 10.03 143.22 727.56
min 10.00 230.00 1160.00
25% 19.00 304.00 1560.00
50% 24.00 406.00 1990.00
75% 27.00 477.00 2499.00
max 64.00 1013.00 4893.00
In [11]:
# 相関係数
data.corr()
Out[11]:
num_of_sents num_of_words num_of_letter
num_of_sents 1.00 0.72 0.62
num_of_words 0.72 1.00 0.98
num_of_letter 0.62 0.98 1.00
  • 相関係数は-1から+1までの値をとる
  • マイナスの値は負の相関(一方の値が増えるともう一方の値が減る関係)
  • プラスの値は正の相関(一方の値が増えるともう一方の値が増える関係)
  • 0は無相関(2変数間に関係がない)
  • 値の絶対値が関係の強さを表す
In [12]:
# 列の追加
# ここのファイルで1単語あたりの平均文字数を算出して追加
# 単語に含まれる文字数が多いとその単語は難しく、少ないと簡単(一般論)

data["ave_of_word"] = data["num_of_letter"] / data["num_of_words"]
In [13]:
# 列の追加を確認
data.head()
Out[13]:
num_of_sents num_of_words num_of_letter ave_of_word
JPN002.txt 25 392 1969 5.02
JPN004.txt 18 288 1555 5.40
JPN006.txt 26 548 3042 5.55
JPN008.txt 21 332 1747 5.26
JPN010.txt 18 391 1987 5.08

4.4 Pandasを用いたデータの可視化¶

In [14]:
# 可視化のために使用するmatplotlibをimport
import matplotlib.pyplot as plt
# jupyter notebook上で表示するための設定
%matplotlib inline

# 1文字あたりの平均語数のヒストグラム
data["ave_of_word"].plot(kind="hist")

# ヒストグラムは
# 高さが度数(人数とか)。
# 棒グラフと似てるけど違う。
# 量的データの全体的な傾向を表す。
Out[14]:
<AxesSubplot: ylabel='Frequency'>
No description has been provided for this image
In [15]:
# 散布図は
# 2つの変数の関係を視覚化する。
# ここでは横軸が総文数、縦軸が1単語あたりの平均文字数
# 難しい単語を知っているからといって、長い文が書けるわけではなさそう。
data.plot(kind="scatter",x="num_of_sents",y="ave_of_word")
Out[15]:
<AxesSubplot: xlabel='num_of_sents', ylabel='ave_of_word'>
No description has been provided for this image
In [16]:
# 語数の箱ひげ図
# 箱ひげ図も全体的な傾向を示す
data["num_of_words"].plot(kind="box")
Out[16]:
<AxesSubplot: >
No description has been provided for this image
  • データのばらつきを表現するグラフ。
  • 以下の用語を知らないと分からない。
用語 意味
最小値 データの中で最も小さい値
最大値 データの中で最も大きい値
第一四分位数(1Q) データを小さい方から並べて小さい方から25%のところ
中央値 データを小さい方から並べて小さい方から50%のところ
第三四分位数 (3Q) データを小さい方から並べて小さい方から75%のところ
四分位範囲 (IQR) 第一四分位数 - 第三四分位数
外れ値 四分位範囲±1.5×四分位範囲に入らない値
In [17]:
# 散布図行列
# 散布図とヒストグラムを同時に示した図

from pandas.plotting import scatter_matrix

scatter_matrix(data,alpha=0.5)
Out[17]:
array([[<AxesSubplot: xlabel='num_of_sents', ylabel='num_of_sents'>,
        <AxesSubplot: xlabel='num_of_words', ylabel='num_of_sents'>,
        <AxesSubplot: xlabel='num_of_letter', ylabel='num_of_sents'>,
        <AxesSubplot: xlabel='ave_of_word', ylabel='num_of_sents'>],
       [<AxesSubplot: xlabel='num_of_sents', ylabel='num_of_words'>,
        <AxesSubplot: xlabel='num_of_words', ylabel='num_of_words'>,
        <AxesSubplot: xlabel='num_of_letter', ylabel='num_of_words'>,
        <AxesSubplot: xlabel='ave_of_word', ylabel='num_of_words'>],
       [<AxesSubplot: xlabel='num_of_sents', ylabel='num_of_letter'>,
        <AxesSubplot: xlabel='num_of_words', ylabel='num_of_letter'>,
        <AxesSubplot: xlabel='num_of_letter', ylabel='num_of_letter'>,
        <AxesSubplot: xlabel='ave_of_word', ylabel='num_of_letter'>],
       [<AxesSubplot: xlabel='num_of_sents', ylabel='ave_of_word'>,
        <AxesSubplot: xlabel='num_of_words', ylabel='ave_of_word'>,
        <AxesSubplot: xlabel='num_of_letter', ylabel='ave_of_word'>,
        <AxesSubplot: xlabel='ave_of_word', ylabel='ave_of_word'>]],
      dtype=object)
No description has been provided for this image

4.5 データの読み込みと追加¶

  • 新たなファイルを読み込み既存のデータフレームに追加します。
  • ここでは学習者の作文に付与された評価のデータを読み込みます。
  • 評価データはここで特徴量を抽出した作文を書いていない学習者の情報が含まれていますので、正規表現を用いて必要な評価データだけを抽出します。
In [33]:
# csvからデータの読み込み
eva = pd.read_csv("../DATA02/eva.csv",index_col=0)
eva.head()
Out[33]:
evaluation
JPN002 3
JPN004 3
JPN006 5
JPN008 3
JPN010 3
In [34]:
import re
# 必要なデータの取り出し

f_names = []

for f in F:
    obj = re.sub(r"\.txt","",f)
    f_names.append(obj)

EVA = []

for i in f_names:
    EVA.append(int(eva.loc[i]))
/var/folders/00/8293wff96nvck2xsxdt2vc4w0000gn/T/ipykernel_42795/3818513998.py:13: FutureWarning: Calling int on a single element Series is deprecated and will raise a TypeError in the future. Use int(ser.iloc[0]) instead
  EVA.append(int(eva.loc[i]))
In [36]:
# 既存のデータフレームに評価の列の追加
data["eva"] = EVA
data.head()
Out[36]:
num_of_sents num_of_words num_of_letter ave_of_word eva
JPN002.txt 25 392 1969 5.02 3
JPN004.txt 18 288 1555 5.40 3
JPN006.txt 26 548 3042 5.55 5
JPN008.txt 21 332 1747 5.26 3
JPN010.txt 18 391 1987 5.08 3

4.6 カテゴリごとの統計量¶

  • 5.5で読み込んだ評価データを用いて、評価ごとに統計量を算出します。
In [37]:
# 平均値
data.groupby("eva").mean()
Out[37]:
num_of_sents num_of_words num_of_letter ave_of_word
eva
2 28.71 353.86 1636.86 4.68
3 23.00 363.13 1854.48 5.13
4 29.14 497.50 2471.07 5.00
5 25.80 556.00 3111.00 5.59

練習問題¶

../DATA02/txt/にあるファイルのそれぞれで総文数、総語数、総文字数を算出し、DataFrameに保存し、基本統計量を算出しなさい。このDataframeを用いて、総文数のヒストグラム、総語数の箱ひげ図、総文数と総文字数の散布図を描画しなさい。