微忘録

好奇心に記憶力がついていかない人のブログ

BigQueryだけで統計的因果推論を行いたかった

この記事はRettyアドベントカレンダー12日目の記事です。
昨日は櫻井さんの「KubernetesでVolumeトリックを行う方法」でした。

はじめに

Retty株式会社でデータアナリストとしてお仕事をしています。 6日目の「あなたのエリアは何処から? ~地理空間クラスタリングとの差分検証~」の記事と同様に、箸休めの記事になります。 それでは表題通り、BigQueryだけで統計的因果推論を行いたかった話です。

ことの発端

BigQueryは高速処理や豊富な関数など、それ自体でも強力なツールです。最近ではBigQueryMLによる機械学習の実施も可能になり、利用できるモデルの数も増え続けています。

cloud.google.com

Rettyではデータの民主化を進めており、アナリストだけではなくプランナーもBigQueryを活用しています。そのためBigQueryの可能範囲が分かることは重要ですし、時には背伸びをした使い方をアナリストが試す必要があります。そのため今回はBigQueryMLの可能範囲について、統計的因果推論の手法が用いれるか実験したいと思います。


いざ挑戦

今回は岩波DSvol3よりサンプルデータを拝借して、CM視聴とアプリ利用の因果効果を推定します。カラム構成は主に以下です。詳細については書籍をご一読ください。

  • gamedummy…アプリ利用
  • gamesecond…アプリ利用秒数
  • gamecount…アプリ利用回数
  • cm_dummy…CM接触有無
  • etc...

①データの準備

BigQueryでは外部データソースを参照したテーブルを用意することが可能です。
今回はGoogleDriveにデータを設置し、外部データソースとして認識させます。

cloud.google.com

②BigQueryMLによる2項ロジスティック回帰と傾向スコア算出

傾向スコアを算出したい「CM接触有無(cm_dummy)」をlabel(目的変数)に、ゲーム利用関連の列を除き、その他の変数を説明変数にBigQueryMLを実行します。

create model `project_name.sandbox.ml_adv_logreg` 
options(model_type='logistic_reg', auto_class_weights=true, early_stop = true) as
select
 * except(cm_dummy, gamedummy, gamesecond, gamecount, id)
 , cm_dummy as label
from `project_name.sandbox.adventcalendar`
;

次にモデルの結果画面を確認します。モデル評価に必要な情報はおおよそこの画面で確認できます。陽性クラスの閾値も動的に確認でき、その際には下記のプロットが動的に変動します。

f:id:wtnVenga:20191212173900p:plain
モデル結果画面①

f:id:wtnVenga:20191212173957p:plain
モデル結果画面②

モデルが傾向スコアの算出に有用なのか否かについての検討を行います。 まずはAUCが0.78程度あるためモデルのfittingはある程度は良しと考え、次に実際に傾向スコアの推論を行い、分布の対称性があるか確認します。

with pred as ( 
  select *
  from ml.predict(
    model `project_name.sandbox.ml_adv_logreg`,(
    select * except(cm_dummy, gamedummy, gamesecond, gamecount), cm_dummy as label
    from `project_name.sandbox.adventcalendar`),
    struct(0.5192 as threshold)
  )
)

select pred.* except(predicted_label_probs) , _p.prob
from pred, unnest(pred.predicted_label_probs) as _p
where pred.predicted_label = _p.label

ML.PREDICT 関数  |  BigQuery ML  |  Google Cloud

傾向スコアとしてのヒストグラムの対称性を、一旦spreadsheetに出力して確認します。ある程度の分布としての重なりが確認できるため、最後の段階へと移ります。 f:id:wtnVenga:20191212181920p:plain f:id:wtnVenga:20191212181927p:plain

ちなみに単に実行結果を出力するだけだと、以下のような結果が得られます。

f:id:wtnVenga:20191212175118p:plain
ml.predictの実行結果

③統計的因果推論に用いる推定量への変換と検証

ここで問題が発生します。

あれ・・・IDを変数に入れていないからJOINできないぞ・・・?

上記のクエリを拡張し、推定量への変換とATTの算出を行いたかったのですが、説明変数に「ID」を含める御法度をしなければ、predict結果を元テーブルとJOINすることができません。

真の原因変数を用いれないので、層別解析、傾向スコアマッチングさえ用いることは困難になりました。

最後に

どこかにJOINできるINDEXが隠されているのでしょうか。 今回以外にもRMSEやMAEなど様々な誤差指標を算出したい場合はどうすればよいのでしょうか。 本記事はここまでですが、個人的に解決策を探し続けます。情報提供もお待ちしております。

pandasでtimezoneを含むstringレコードをdatetimeに型変換する方法

pandasのto_datetime()関数はtimezoneフォーマットの%zに対応していなかったので、解決策を備忘録。

t.co

github.com

解決策

今回はnginxログで23/Apr/2018:14:21:43 +0000 形式の文字列を例として用います。 解決方法は要は、datetime.strptime()関数で推論できる状態に整えてから、to_datetime()関数に与えてあげます。

import pandas as pd
from datetime import datetime

df = pd.DataFrame({'datetime_like_column' : pd.Series(['23/Apr/2018:14:21:43 +0000', '29/Dec/2018:03:36:27 +0000'])})

f = lambda x: datetime.strptime(x, '%d/%b/%Y:%H:%M:%S %z')

#datetimeもしくはdate型かつタイムゾーンはutc指定
df['datetime_like_column_as_datetime'] = pd.to_datetime(df['datetime_like_column'].apply(f), utc=True)
df['datetime_like_column_as_date'] = pd.to_datetime(df['datetime_like_column'].apply(f), utc=True).dt.date
df
datetime_like_column datetime_like_column_as_datetime datetime_like_column_as_date
0 23/Apr/2018:14:21:43 +0000 2018-04-23 14:21:43+00:00 2018-04-23
1 29/Dec/2018:03:36:27 +0000 2018-12-29 03:36:27+00:00 2018-12-29

終わりに

今回扱った問題はpandas の major release 0.24.0 で修正されるようです。 f:id:wtnVenga:20190107005004p:plain

また、同一カラムに複数のtimezoneがある場合の変換方法については、以下をご参照ください。 (indexカラムとして扱うことで攻略できるらしいです)

note.nkmk.me

numpyオブジェクトをjson.dumpできるようにエンコーダーを拡張する方法

最近、友人の運営する大学講義の検索サイトを、機械学習で良い感じにしてます。

地味に悩んだのが「JSON形式でのデータ入出力」の部分。具体的には、「JSON形式で渡されたデータに、推定結果の一部numpyオブジェクトを加えたJSONデータを返す」作業。

Pythonなど動的言語はデータ型をよしなに判断してくれますが、少し例外に触れた時には指定してあげる必要があります。
今回はその例外のためにクラスを自作して指定する方法を備忘録。

問題

何も引数に指定せず、ただ以下の様にjson.dumps()メソッドにnumpy.int64などを渡すと、以下の様なエラーに遭遇する。

json.dumps(numpy.int64(111))
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-12-c21d581d4bed> in <module>()
----> 1 json.dumps(np.int64(111))


/usr/local/var/pyenv/versions/anaconda3-5.0.0/lib/python3.6/json/__init__.py in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    229         cls is None and indent is None and separators is None and
    230         default is None and not sort_keys and not kw):
--> 231         return _default_encoder.encode(obj)
    232     if cls is None:
    233         cls = JSONEncoder


/usr/local/var/pyenv/versions/anaconda3-5.0.0/lib/python3.6/json/encoder.py in encode(self, o)
    197         # exceptions aren't as detailed.  The list call should be roughly
    198         # equivalent to the PySequence_Fast that ''.join() would do.
--> 199         chunks = self.iterencode(o, _one_shot=True)
    200         if not isinstance(chunks, (list, tuple)):
    201             chunks = list(chunks)


~~~

TypeError: Object of type 'int64' is not JSON serializable

この一文は「JSONモジュールの既存エンコーダーに対応している型じゃないから変換無理です」という話

TypeError: Object of type 'int64' is not JSON serializable

なので既存エンコーダーに、自前のエンコード用クラスを与えて拡張する必要があります。

解決策

以下のような、型の種類ごとに変換を行うクラスを作成して、JSONモジュールに対応する型にします。

  • isinstance(object, class)で、一致する場合はreturn以下で変換して返す。
  • numpy.integerint型へ
  • numpy.floatingfloat型へ
  • numpy.ndarraylist型へ

(今回はint型の変換だけで良いのですが、オマケでその他も加えておきます。)

import numpy
import json

class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, numpy.integer):
            return int(obj)
        elif isinstance(obj, numpy.floating):
            return float(obj)
        elif isinstance(obj, numpy.ndarray):
            return obj.tolist()
        else:
            return super(MyEncoder, self).default(obj)

そしてjson.dump()cls引数にクラスを渡してあげます。

json.dumps(numpy.int64(111),cls = MyEncoder)
'111'

解決!

最後に

JSON形式で数値を扱うこと自体ナンセンスかもしれませんが、遭遇しやすい問題かなと思うので載せておきました。 良き分析ライフを御過ごしください。

参考記事

以下記事を参考にしております。先人の知恵に感謝です。

GitHubのセキュリティ設定でやるべきこと

GitHub周りのセキュリティ設定、何回忘れたのかさえ忘れました。 公開鍵でSSH接続して、EmailをPrivateするだけの備忘録です。

※厳密なセキュリティ設定は記述しないので、他の猛者の方々の記事を参考にして下さい。


アカウントと公開鍵の設定

鍵セットの作成

rsa指定で鍵セットを作成。既存がある際は重複削除しないよう注意。

$ cd ~/.ssh #移動して
$ ssh-keygen -t rsa -C mymail@example.com #鍵作成。-C以下任意で自分のメールアドレス

SSH連携

ssh/configに記入し、GitHubとのssh接続を楽にする。

$ vim ~/.ssh/config 

#以下をインサート
Host github.com
  HostName github.com
  IdentityFile ~/.ssh/id_rsa
  User git

公開鍵をクリップボードにコピーして、GitHubアカウントのSSH keysでコピペして登録。

$ pbcopy < ~/.ssh/id_rsa.pub

接続成功したか確認。成功したら以下のような文が出てくる

$ ssh -T git@github.com
Hi wtnVenga! You've successfully authenticated, but GitHub does not provide shell access.

ローカルリポジトリのconfig設定

ローカルリポジトリに移動。config設定を開いて、https://~git@~に変更。

$ cd [local repository]
$ vim .git/config

#'url = https://github.com/[user_name]/[repository_name].git' の部分を以下形式に変更。
url = git@github.com:[user_name]/[repository_name].git

PrivateEmailの利用

GithubのEmail設定をPrivate化するとEmail情報を守れる。
(※リモートリポジトリをcloneされた時にcommitログから漏れない。)

GitHubアカウントの設定

まずはEmail設定の Keep my email address privateBlock command line pushes that expose my email にチェック。 表示される[userid]+[username]@users.noreply.github.com をコピペ。

git configの設定

git configのemail設定を書き換える(--global--local かはお好みで)

$ git config --global user.email [userid]+[username]@users.noreply.github.com

(commitログ書き換え)

最新コミットログの変更。(開いたら:ZZで保存するだけ)

git commit --amend --reset-author

過去コミットログの変更。自分のSHA(コミットハッシュ値から)を指定

git rebase -i [my_SHA] -x "git commit --amend --reset-author -CHEAD"

参考元

git - Meaning of the GitHub message: push declined due to email privacy restrictions - Stack Overflow

How to amend several commits in Git to change author - Stack Overflow

gensimのmodels.TfidfModel()で、引数にSMART notationが使えるようになっていた話

つい先日こんなツイートをしたところ、爆速で公式からリプライが来ました。

「2018年1月2日のgensim3.3.0のリリースに際して、TfidfModelにSMART notation機能が追加されたから宜しくな!」という話で、つまり「models.TfidfModel()コーパスの重み付け手法の選択が、簡単な記法でできるsmartirs引数を新たに用意したよ」という要旨です。今回はこの機能について宣伝と備忘録。

gensimとTF-IDF法

gensimについて

gensimはLSIやLDAなどのトピック分析を行う際に便利なPythonモジュールです。トピック分析の発端である自然言語処理での活用と主しており、文書ごと単語リスト*1(配列)を用意すれば、gensimモジュールの関数を用いて、辞書とコーパスの作成からトピック推定と評価まで可能です。

TF-IDFについて

そして今回備忘録をするmodels.TfidfModel()は、コーパス作成時に単語にTF-IDF法を用いた重み付けをする関数です。

TFは"Term Frequency"つまり文書内単語頻度、IDFは"Inverse Document Frequency"つまり逆文書頻度、そしてTF-IDFはそれら2種の重み付けの積で、「全文書内での出現頻度は低いが、特定文書にて頻繁に用いられる特定単語に、強い重み付けができる」方法です。

そしてgensim3.3.0以前までのmodels.TfidfModel()ではTF-IDF法のみしか指定できず、「TF法だけ」もしくは「IDF法だけ」は自作する必要がありました。*2 この面倒くささを改善したのが、今回のSMART notation機能の導入です。

SMART notationを試す

SMART notationとは

SMART (System for the Mechanical Analysis and Retrieval of Text) notationとは、「文書検索システムにおける重み付けの簡便表記法」になります。

伝統的なベクトル空間モデルを用いた文書検索では、クエリ内容(文書名)に対して、各文書の単語群(Bag-of-Words)の出現頻度にコサイン類似度を用いた計算により、類似性の高い文書を検索結果として表示します。この時の検索結果を左右するのが、TF-IDF法などによる文書内単語群それぞれ対する重み付けです。*3

そしてこの文書内単語群に行われる重み付けを簡便的に表記法する方法が"SMART notation"であり、以下一覧のような表記になります。*4 f:id:wtnVenga:20180208115703p:plain

ここから"単語""文書""正規化"の順に記号を3つ重ねるだけで、どのような重み付けがされるのかが一目瞭然になります。

gensim3.3.0でサンプルコード

そしてmodels.TfidfModel()に"smartirs"引数が追加されたことでSMART notationが利用可能になりました。以下のように重み付けを指定することができます。

from gensim.corpora import Dictionary
from gensim.models import TfidfModel

documents = ["Human machine interface for lab abc computer applications",
              "A survey of user opinion of computer system response time",
              "The EPS user interface management system",
              "System and human system engineering testing of EPS",
              "Relation of user perceived response time to error measurement",
              "The generation of random binary unordered trees",
              "The intersection graph of paths in trees",
              "Graph minors IV Widths of trees and well quasi ordering",
              "Graph minors A survey"]

# 以下の7語をストップワードとして定義
stop_words = set('for a of the and to in'.split())

# 文を単語に分割し、ストップワードを除去した配列を作成
texts = [[word for word in document.lower().split() if word not in stop_words] for document in documents]

#辞書とコーパスの作成
dictionary = Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]

#"n"(tf法)と、"t"(idf法)で、"c"(コサイン正規化)した重み付け
model = TfidfModel(corpus, id2word=dictionary, smartirs="ntc")
vectorized_corpus = list(model[corpus])

最後に

gensim3.3.0は最新版のAnaconda3-5.0.0では未だ対応していないので、別途"pip install"する必要があります。早く追加されて欲しいところ。

*1:厳密化すると「単語(Term)」よりも、字句解析における「トークン(token)」のことです。形態素解析などを用いても必ずしも意味ある単語にすることは難しく、あくまでも文字のカタマリとして「トークン」と呼ばれます。

*2:L2正規化するか否かの"normalise"引数は既存

*3:https://nlp.stanford.edu/IR-book/html/htmledition/queries-as-vectors-1.html#eqn:cosinescore

*4:https://nlp.stanford.edu/IR-book/html/htmledition/document-and-query-weighting-schemes-1.html

MALLETラッパーを用いた、gensimでの"Gibbs sampler"によるトピック推定を試す

LSIやLDAなどのトピック分析をPythonで実行するなら、gensimモジュールの利用が一般的です。しかしgensimでは変分ベイズ法による推定しか基本的に対応しておらず、その他のギブスサンプリングなどを用いるには少し工夫が必要です。

そこで今回はgensimにあるgensim.models.wrappers.LdaMallet()という、java製のオープンソースソフトであるMALLETのラッパー機能を用いて、Pythonかつgensimの環境でギブスサンプリングによるトピック推定をざっくりと流れだけ試します。

崩壊型ギブスサンプリングはldaモジュールで可能です。現行のscikit-learnモジュールのLDAは変分ベイズのみです。ご注意を。

なお以下の環境で準備します。詳細については適宜ググってください。

MALLETの準備

MALLETをダウンロードして、Apach antでディレクトリをビルドするだけ

  1. MALLETのダウンロード(こちらから Downloading MALLET
  2. MALLETのディレクトリにてantコマンドでビルド
  3. "BUILD SUCCESSFUL"が表示されたらビルド成功
  4. ディレクトリのパスを"Users/~.../mallet-2.0.8/bin/mallet"まで記憶

Pythonの実行

文書ごと単語リストの用意

トピック分析したい文書群(documents)リストを用意し、ストップワード除去を行う。
gensimチュートリアルを引用

documents = ["Human machine interface for lab abc computer applications",
              "A survey of user opinion of computer system response time",
              "The EPS user interface management system",
              "System and human system engineering testing of EPS",
              "Relation of user perceived response time to error measurement",
              "The generation of random binary unordered trees",
              "The intersection graph of paths in trees",
              "Graph minors IV Widths of trees and well quasi ordering",
              "Graph minors A survey"]

# 以下の7語をストップワードとして定義
stop_words = set('for a of the and to in'.split())

# 文を単語に分割し、ストップワードを除去した配列を作成
texts = [[word for word in document.lower().split() if word not in stop_words] for document in documents]

以下のような中身の単語リスト(texts)に格納されました。

    [['human', 'machine', 'interface', 'lab', 'abc', 'computer', 'applications'], ['survey', 'user', 'opinion', 'computer', 'system', 'response', 'time']]

辞書とコーパスの作成

gensimモジュールを呼び出し、文書ごと単語リストを元に辞書とコーパスを作成します。

from gensim import corpora, models
import gensim

#辞書とコーパスを作成
dictionary = corpora.Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]

#コーパスをMALLET用の形式に変換します。
corpora.malletcorpus.MalletCorpus.serialize("./corpus.mallet", corpus)
mallet_corpus = corpora.malletcorpus.MalletCorpus("./corpus.mallet")

ギブスサンプリングによるトピックモデル推定

ビルドされたMALLETのパス(mallet_path)を指定し、用意した辞書とコーパスで3つのトピックを推定。

mallet_path = "Users/~.../mallet-2.0.8/bin/mallet"
lda = models.wrappers.LdaMallet(mallet_path, mallet_corpus, num_topics=3, id2word=dictionary)

推定されたトピックを確認。各トピック内容の解釈は割愛。

lda.print_topics()
 [(0,
  '0.182*"random" + 0.091*"user" + 0.091*"machine" + 0.091*"binary" + 0.091*"engineering" + 0.091*"paths" + 0.091*"human" + 0.091*"opinion" + 0.091*"minors" + 0.091*"lab"'),
 (1,
  '0.136*"perceived" + 0.136*"interface" + 0.136*"measurement" + 0.045*"survey" + 0.045*"graph" + 0.045*"eps" + 0.045*"time" + 0.045*"trees" + 0.045*"ordering" + 0.045*"testing"'),
 (2,
  '0.176*"quasi" + 0.118*"iv" + 0.118*"computer" + 0.059*"system" + 0.059*"relation" + 0.059*"generation" + 0.059*"management" + 0.059*"time" + 0.059*"response" + 0.059*"human"')]

最後に

今のところ「やってみた」系の記事では変分ベイズでの実装しか見当たらないので備忘録してみました。

gensimには他にも、Biei先生のフォーマットや、GibbsLDA++のフォーマットなどがあります(gensim: Corpora and Vector Spaces)。 そのため「gensimでデータ整形だけして他で実行するぜ!」な強者の方々は、その他の選択肢も是非ご検討ください。

※記事内容の改善点にお気付きの方は、コメントにてご一報いただけますと幸甚です。

2015年度版NLP100本ノック第2章をPython3で解く

前回(2015年度版NLP100本ノック第1章をPython3で解く - 微忘録)の続き。 bashとPython3で。各コードの実行結果は、こちらのリポジトリにて公開しています。

第2章: UNIXコマンドの基礎

hightemp.txtは,日本の最高気温の記録を「都道府県」「地点」「℃」「日」のタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,hightemp.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

10. 行数のカウント

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

%%bash
wc -l ./datasets/hightemp.txt
with open("./datasets/hightemp.txt","r") as hightemp:
    print(len(hightemp.readlines()))

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

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

%%bash
sed -e "s/\t/| /g" ./datasets/hightemp.txt
import re
with open("./datasets/hightemp.txt","r") as hightemp:
    lines = hightemp.readlines()
    print(lines,'\n')
    
    print([re.sub(r'\t'," ",hightemp)for hightemp in lines])

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

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

%%bash
cut -f 1 ./datasets/hightemp.txt > ./datasets/col1.txt
cut -f 2 ./datasets/hightemp.txt > ./datasets/col2.txt
with open("./datasets/hightemp.txt") as hightemp:
    lines = hightemp.readlines()
    
    #col1.txt
    col1 = []
    for line in lines:
        col1.append(line.split()[0] + "\n") #"\n"で単語ごとに改行
        
    with open("./datasets/col1py.txt" , "w") as split:
            split.writelines(col1)
    
    #col2.txt
    col2 = []
    for line in lines:
        col2.append(line.split()[1] + "\n")
        
    with open("./datasets/col2py.txt" , "w") as split:
            split.writelines(col2)
            
    hightemp.close()

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

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

%%bash
paste -d"\t" ./datasets/col1.txt ./datasets/col2.txt
with open("./datasets/col1.txt") as col1_file, open("./datasets/col2.txt") as col2_file:
    col1, col2 = col1_file.readlines(), col2_file.readlines()

with open("./datasets/merged.txt", "w") as writer:
    for a, b in zip(col1, col2):
        writer.write("\t".join([a.rstrip(),b]))
        
for i in open("./datasets/merged.txt"):
    print(i)

14. 先頭からN行を出力

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

%%bash
head -n 5 ./datasets/hightemp.txt
def head(n,file):
    with open(file,'r') as hightemp:
        lines = hightemp.readlines()
        for lineNumber in range(n):
            print(lines[lineNumber])
            
head(5, "./datasets/hightemp.txt")

15. 末尾のN行を出力

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

%%bash
tail -n 5 ./datasets/hightemp.txt
def tail(n,file):
    with open(file,'r') as hightemp:
        lines = hightemp.readlines()
        stopline = len(lines) - n
        for lineNumber in range(stopline,len(lines),1):
            print(lines[lineNumber])
            
tail(5, "./datasets/hightemp.txt")

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

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

%%bash
split -l 5 ./datasets/hightemp.txt ./datasets/n-split_
def splitfunc(file,n):
    with open(file) as filed:
        lines = filed.readlines()
    
    for i in range(n):
        with open("./datasets/splitblock%s.txt" % str(i), "w") as split:
            split.writelines(lines[n*i : n*(i +1)])
            
splitfunc("./datasets/hightemp.txt",3)

with open("./datasets/splitblock2.txt","r") as check:
    print(check.readlines())

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

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

%%bash
cut -f 1 ./datasets/hightemp.txt |sort | uniq
with open("./datasets/hightemp.txt","r") as hightemp:
    lines = hightemp.readlines()
    
    col1 = []
    for line in lines:
        col1.append(line.split()[0] + "\n") #"\n"で単語ごとに改行

    li = set(col1)
    print(sorted(li))

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

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

%%bash
sort -nk3r ./datasets/hightemp.txt
with open("./datasets/hightemp.txt", 'r') as hightemp:
    lines = hightemp.readlines()
    for line in sorted(lines,key=lambda x: x.split()[2], reverse=False):
        print(line)

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

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

%%bash
cut -f 1 ./datasets/hightemp.txt | sort |uniq -c | sort -r
from collections import Counter

with open("./datasets/hightemp.txt","r") as hightemp:
    lines = hightemp.readlines()
    
    li = []
    for line in lines:
        li.append(line.split()[0])
    
    counted = Counter(li)
    print(counted)