【言語処理100本ノック 2020】第2章: UNIXコマンド【Python】

自然言語処理の問題集として有名な言語処理100本ノックの2020年版の解答例です。 この記事では、以下の第1章から第10章のうち、「第2章: UNIXコマンド」を解いてみた結果をまとめています。

環境設定

コードの実行はGoogle Colaboratoryで行います。 以降の解答の実行結果をすべて含むノートブックは、以下のリンクから直接参照することができます。

Open In Colab

第2章: UNIXコマンド

popular-names.txtは,アメリカで生まれた赤ちゃんの「名前」「性別」「人数」「年」をタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,popular-names.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

指定のデータをダウンロードします。 Google Colaboratoryのセル上で下記のコマンドを実行すると、現在のディレクトリに対象のテキストファイルがダウンロードされます。

!wget https://nlp100.github.io/data/popular-names.txt

10. 行数のカウント

行数をカウントせよ.確認にはwcコマンドを用いよ.

本章の趣旨とは異なる気がしますが、今回は実際のデータ分析のシーンで出会うことが多いと思われるpandasを利用します。 まずは、データフレームとして読み込んでから各問の処理を行っていきます。また、問題文の指示に従い、コマンドでの結果の確認も行っています。

import pandas as pd

df = pd.read_table('./popular-names.txt', header=None, sep='\t', names=['name', 'sex', 'number', 'year'])
len(df)
--- 出力 ---
2780

wcはテキストファイルの行数や単語数を数えるためのコマンドです。ここでは-lを指定して、改行数(=行数)をカウントしています。

# 確認
!wc -l ./popular-names.txt
--- 出力 ---
2780 ./popular-names.txt

11. タブをスペースに置換

タブ1文字につきスペース1文字に置換せよ.確認にはsedコマンド,trコマンド,もしくはexpandコマンドを用いよ.

確認のみ行います。

sedコマンドを利用します。-eは処理内容の指定、最初のs/は正規表現を用いるための指示で、最後の/gは正規表現にマッチしたすべての文字列を置換することを意味します。そして、\t/ /でタブをスペースに変換することを表します(変換前 / 変換後)。また、head -n 5で最初の5行を表示しています。間の|sedの出力をheadに渡すための記号です。

!sed -e 's/\t/ /g' ./popular-names.txt | head -n 5
--- 出力 ---
Mary F 7065 1880
Anna F 2604 1880
Emma F 2003 1880
Elizabeth F 1939 1880
Minnie F 1746 1880

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

各行の1列目だけを抜き出したものをcol1.txtに,2列目だけを抜き出したものをcol2.txtとしてファイルに保存せよ.確認にはcutコマンドを用いよ.

col1 = df['name']
col1.to_csv('./col1.txt', index=False)
print(col1.head())

col2 = df['sex']
col2.to_csv('./col2.txt', index=False)
print(col2.head())
--- 出力 ---
0         Mary
1         Anna
2         Emma
3    Elizabeth
4       Minnie
Name: name, dtype: object

0    F
1    F
2    F
3    F
4    F
Name: sex, dtype: object
# 確認
!cut -f 1 ./popular-names.txt | head -n 5  # popular-names.txtの1列目の最初の5行
--- 出力 ---
Mary
Anna
Emma
Elizabeth
Minnie
!cut -f 2 ./popular-names.txt | head -n 5  # popular-names.txtの2列目の最初の5行
--- 出力 ---
F
F
F
F
F

13. col1.txtとcol2.txtをマージ

12で作ったcol1.txtとcol2.txtを結合し,元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ.確認にはpasteコマンドを用いよ.

col1 = pd.read_table('./col1.txt')
col2 = pd.read_table('./col2.txt')
merged_1_2 = pd.concat([col1, col2], axis=1)
merged_1_2.to_csv('./merged_1_2.txt', sep='\t', index=False)
merged_1_2.head()
--- 出力 ---
        name sex
0       Mary   F
1       Anna   F
2       Emma   F
3  Elizabeth   F
4     Minnie   F
# 確認
!paste ./col1_chk.txt ./col2_chk.txt | head -n 5
--- 出力 ---
Mary    F
Anna    F
Emma    F
Elizabeth   F
Minnie  F

14. 先頭からN行を出力

自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.確認にはheadコマンドを用いよ.

N = input('>> ')
df.head(int(N))
--- 出力 ---
         name sex  number  year
1340    Linda   F   99689  1947
1360    Linda   F   96211  1948
1350    James   M   94757  1947
1550  Michael   M   92704  1957
1351   Robert   M   91640  1947
# 確認
!head -n 5 ./popular-names.txt
--- 出力 ---
Mary    F   7065    1880
Anna    F   2604    1880
Emma    F   2003    1880
Elizabeth   F   1939    1880
Minnie  F   1746    1880

15. 末尾のN行を出力

自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.確認にはtailコマンドを用いよ.

N = input('>> ')
df.tail(int(N))
--- 出力 ---
          name sex  number  year
2775  Benjamin   M   13381  2018
2776    Elijah   M   12886  2018
2777     Lucas   M   12585  2018
2778     Mason   M   12435  2018
2779     Logan   M   12352  2018
# 確認
!tail -n 5 ./popular-names.txt
--- 出力 ---
Benjamin    M   13381   2018
Elijah  M   12886   2018
Lucas   M   12585   2018
Mason   M   12435   2018
Logan   M   12352   2018

16. ファイルをN分割する

自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.同様の処理をsplitコマンドで実現せよ.

いろいろなやり方があると思いますが、ここではレコードの通番に対して、N分位点を算出するqcutを適用することでファイルをN分割するフラグを付与しています。

def split_file(N):
  tmp = df.reset_index(drop=False)
  df_cut = pd.qcut(tmp.index, N, labels=[i for i in range(N)])
  df_cut = pd.concat([df, pd.Series(df_cut, name='sp')], axis=1)

  return df_cut

N = input('>> ')
df_cut = split_file(int(N))
df_cut['sp'].value_counts()
--- 出力 ---
9    278
8    278
7    278
6    278
5    278
4    278
3    278
2    278
1    278
0    278
Name: sp, dtype: int64
df_cut.head()
--- 出力 ---
        name sex  number  year sp
0       Mary   F    7065  1880  0
1       Anna   F    2604  1880  0
2       Emma   F    2003  1880  0
3  Elizabeth   F    1939  1880  0
4     Minnie   F    1746  1880  0
# コマンドによる分割
!split -l 200 -d ./popular-names.txt sp

17. 1列目の文字列の異なり

1列目の文字列の種類(異なる文字列の集合)を求めよ.確認にはcut, sort, uniqコマンドを用いよ.

指定の列で重複を除き、行数をカウントしています。

len(df.drop_duplicates(subset='name'))
--- 出力 ---
136
# 確認
!cut -f 1 ./popular-names.txt | sort | uniq | wc -l
--- 出力 ---
136

18. 各行を3コラム目の数値の降順にソート

各行を3コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ).確認にはsortコマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい).

df.sort_values(by='number', ascending=False, inplace=True)
df.head()
--- 出力 ---
         name sex  number  year
1340    Linda   F   99689  1947
1360    Linda   F   96211  1948
1350    James   M   94757  1947
1550  Michael   M   92704  1957
1351   Robert   M   91640  1947
# 確認
!cat ./popular-names.txt | sort -rnk 3 | head -n 5
--- 出力 ---
Linda   F   99689   1947
Linda   F   96211   1948
James   M   94757   1947
Michael M   92704   1957
Robert  M   91640   1947

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる

各行の1列目の文字列の出現頻度を求め,その高い順に並べて表示せよ.確認にはcut, uniq, sortコマンドを用いよ.

df['name'].value_counts()
--- 出力 ---
James      118
William    111
Robert     108
John       108
Mary        92
          ... 
Crystal      1
Rachel       1
Scott        1
Lucas        1
Carolyn      1
Name: name, Length: 136, dtype: int64
# 確認
!cut -f 1 ./popular-names.txt | sort | uniq -c | sort -rn
--- 出力 ---
    118 James
    111 William
    108 Robert
    108 John
     92 Mary

理解を深めるためのオススメ教材

全100問の解答はこちら