2022年10月11日火曜日

ワードクラウドを作成する【基本編】

 Colaboratoryで自然言語処理の基本みたいなワードクラウドを生成することができるので使ってみた。

1.そのまま使う(英語のみ)

ワードクライドを作成するライブラリのWordCloudを使うだけならこう。

結果はこんな感じになる。
"the"や"over"が入っていないのは、組み込みのstopwordsに指定されており、画像生成時に削除されているから。
  1. #Colabで使ってDriveに出力する場合
  2. from wordcloud import WordCloud
  3. txt = "The quick brown fox jumps over the lazy dog"
  4. a = WordCloud()
  5. a.generate(text=txt)
  6. #to_fileメソッドで書き出し
  7. a.to_file("/content/drive/MyDrive/tmp/new.png")
wordcloudでそのまま生成したもの
生成例1

stopwordsに"quick"を追加したこのコードでやるとこうなる。
  1. #Colabで実行する場合
  2. #ビルドインのSTOPWORDSをインポートする
  3. from wordcloud import WordCloud, STOPWORDS
  4. txt = "The quick brown fox jumps over the lazy dog"
  5. #brownをstopwordsに加える
  6. stop = set(STOPWORDS)
  7. stop.add("brown")
  8. a = WordCloud(stopwords=stop)
  9. a.generate(text=txt)
  10. a.to_file("/content/drive/MyDrive/tmp/newst.png")
"brown"をstopwordsに加えて生成したもの
生成例2

ワードクラウドでの文字の大きさはword-ranksと出現頻度で決定される。出現頻度への文字の大きさの影響を変更するときはインスタンス作成時にrelative_scalingへ値を与える。
遥かなティペラリー(It's a Long Way to Tipperary)のサビ部分を例にやるとこう。
relative_scaling=0のパラメーターで生成したもの
relative_scaling=0

relative_scaling=0.5のパラメーターで生成したもの
relative_scaling=0.5

relative_scaling=1.0のパラメーターで生成したもの
relative_scaling=1.0

relative_scaling以外で気になったパラメータを公式ドキュメントから抜粋する。
  • height: int (default=200)
    • 画像の縦幅の設定
  • width: int (default=400)
    • 画像の横幅の設定
  • scale: float (default=1)
    • 描写のスケールを設定
  • min_word_length: int, default=0
    • ワードクラウドに含まれるための最低の単語の長さ
  • collocation_threshold: int, default=30 
    • コロケーション(単語と単語のつながり)の判定地
それ以外は公式ドキュメント参照。

2.日本語で使う

wordcloudに日本語辞書は入っていない上に、日本語からワードクラウドを作成するために必要な分かち書きや瀕死の分類などができない。
そこで日本語の形態素解析ツールであるJanomeを使う。
Janomeで簡単に形態素解析を行うには、Tokenizerを使う。日本国憲法前文について、解析を行うとこうなる。
  1. from janome.tokenizer import Tokenizer
  2. txt = """
  3. 日本国民は正当に選挙された国会における代表者を通じて行動し、われらとわれらの子孫のために、諸国民と協和による成果と、わが国全土にわたって自由のもたらす恵沢を確保し、政府の行為によって再び戦争の惨禍が起こることのないようにすることを決意し、ここに主権が国民に存することを宣言し、この憲法を確定する。そもそも国政は国民の厳粛な信託によるものであって、その権威は国民に由来し、その権力は国民の代表者がこれを行使し、その福利は国民がこれを享受する。これは人類普遍の原理であり、この憲法は、かかる原理に基づくものである。われらはこれに反する一切の憲法、法令及び詔勅を排除する。
  4. 日本国民は、恒久の平和を念願し、人間相互の関係を支配する崇高な理想を深く自覚するのであって、平和を愛する諸国民の公正と信義を信頼して、われらの安全と生存を保持しようと決意した。われらは平和を維持し、専制と隷従、圧迫と偏狭を地上から永遠に除去しようと努めている国際社会において、名誉ある地位を占めたいと思う。われらは全世界の国民が、ひとしく恐怖と欠乏から免れ、平和の内に生存する権利を有することを確認する。
  5. われらは、いずれの国家も、自国のことのみに専念して他国を無視してはならないのであって、政治道徳の法則は、普遍的なものであり、この法則に従うことは、自国の主権を維持し、他国と対等関係に立とうとする各国の責務であると信ずる。
  6. 日本国民は、国家の名誉にかけて、全力をあげて崇高な理想と目的を達成することを誓う。
  7. """
  8. txt = txt.replace('\n', '')
  9. t = Tokenizer()
  10. for token in t.tokenize(txt):
  11. print(token)
  12. ###出力###
  13. """
  14. 日本 名詞,固有名詞,地域,国,*,*,日本,ニッポン,ニッポン
  15. 国民 名詞,一般,*,*,*,*,国民,コクミン,コクミン
  16. は 助詞,係助詞,*,*,*,*,は,ハ,ワ
  17. 正当 名詞,形容動詞語幹,*,*,*,*,正当,セイトウ,セイトー
  18. に 助詞,副詞化,*,*,*,*,に,ニ,ニ
  19. 選挙 名詞,サ変接続,*,*,*,*,選挙,センキョ,センキョ
  20. さ 動詞,自立,*,*,サ変・スル,未然レル接続,する,サ,サ
  21. れ 動詞,接尾,*,*,一段,連用形,れる,レ,レ
  22. た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
  23. 国会 名詞,一般,*,*,*,*,国会,コッカイ,コッカイ
  24. における 助詞,格助詞,連語,*,*,*,における,ニオケル,ニオケル
  25. 代表 名詞,サ変接続,*,*,*,*,代表,ダイヒョウ,ダイヒョー
  26. 者 名詞,接尾,一般,*,*,*,者,シャ,シャ
  27. を通じて 助詞,格助詞,連語,*,*,*,を通じて,ヲツウジテ,ヲツージテ
  28. 行動 名詞,サ変接続,*,*,*,*,行動,コウドウ,コードー
  29. し 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
  30. 、 記号,読点,*,*,*,*,、,、,、
  31. われ 名詞,代名詞,一般,*,*,*,われ,ワレ,ワレ
  32. ら 名詞,接尾,一般,*,*,*,ら,ラ,ラ
  33. と 助詞,並立助詞,*,*,*,*,と,ト,ト
  34. われ 名詞,代名詞,一般,*,*,*,われ,ワレ,ワレ
  35. ら 名詞,接尾,一般,*,*,*,ら,ラ,ラ
  36. """

単語自体はtoken.surfaceで取り出せるが、これでは、様々な品詞が混じっているうえ、「日本国民」が「日本」と「国民」に分割されていたりしており、このままワードクラウドに用いることはできない。
そこで、Analizerとtokenfilterを用いる。
  1. from janome.tokenizer import Tokenizer
  2. from janome.analyzer import Analyzer
  3. from janome.tokenfilter import *
  4. txt = """
  5. 日本国民は正当に選挙された国会における代表者を通じて行動し、われらとわれらの子孫のために、諸国民と協和による成果と、わが国全土にわたって自由のもたらす恵沢を確保し、政府の行為によって再び戦争の惨禍が起こることのないようにすることを決意し、ここに主権が国民に存することを宣言し、この憲法を確定する。そもそも国政は国民の厳粛な信託によるものであって、その権威は国民に由来し、その権力は国民の代表者がこれを行使し、その福利は国民がこれを享受する。これは人類普遍の原理であり、この憲法は、かかる原理に基づくものである。われらはこれに反する一切の憲法、法令及び詔勅を排除する。
  6. 日本国民は、恒久の平和を念願し、人間相互の関係を支配する崇高な理想を深く自覚するのであって、平和を愛する諸国民の公正と信義を信頼して、われらの安全と生存を保持しようと決意した。われらは平和を維持し、専制と隷従、圧迫と偏狭を地上から永遠に除去しようと努めている国際社会において、名誉ある地位を占めたいと思う。われらは全世界の国民が、ひとしく恐怖と欠乏から免れ、平和の内に生存する権利を有することを確認する。
  7. われらは、いずれの国家も、自国のことのみに専念して他国を無視してはならないのであって、政治道徳の法則は、普遍的なものであり、この法則に従うことは、自国の主権を維持し、他国と対等関係に立とうとする各国の責務であると信ずる。
  8. 日本国民は、国家の名誉にかけて、全力をあげて崇高な理想と目的を達成することを誓う。
  9. """
  10. txt = txt.replace('\n', '')
  11. t = Tokenizer()
  12. filters = [CompoundNounFilter(), POSKeepFilter('名詞'), POSStopFilter(['非自立','代名詞'])]
  13. NG = ['非自立', '代名詞']
  14. analyzer = Analyzer(tokenizer=t, token_filters=filters)
  15. for token in analyzer.analyze(txt):
  16. #POSStopFilterがうまく機能しない部分があるので二重にフィルタリングする
  17. ex = token.extra[0].split(',')
  18. if all(x not in ex for x in NG):
  19. print(token)
  20. ###出力###
  21. '''
  22. 日本国民 名詞,複合,*,*,*,*,日本国民,ニッポンコクミン,ニッポンコクミン
  23. 正当 名詞,形容動詞語幹,*,*,*,*,正当,セイトウ,セイトー
  24. 選挙 名詞,サ変接続,*,*,*,*,選挙,センキョ,センキョ
  25. 国会 名詞,一般,*,*,*,*,国会,コッカイ,コッカイ
  26. 代表者 名詞,複合,*,*,*,*,代表者,ダイヒョウシャ,ダイヒョーシャ
  27. 行動 名詞,サ変接続,*,*,*,*,行動,コウドウ,コードー
  28. 子孫 名詞,一般,*,*,*,*,子孫,シソン,シソン
  29. 国民 名詞,一般,*,*,*,*,国民,コクミン,コクミン
  30. 協和 名詞,サ変接続,*,*,*,*,協和,キョウワ,キョーワ
  31. 成果 名詞,一般,*,*,*,*,成果,セイカ,セイカ
  32. わが国全土 名詞,複合,*,*,*,*,わが国全土,ワガクニゼンド,ワガクニゼンド
  33. 自由 名詞,形容動詞語幹,*,*,*,*,自由,ジユウ,ジユー
  34. 恵沢 名詞,一般,*,*,*,*,恵沢,ケイタク,ケイタク
  35. 確保 名詞,サ変接続,*,*,*,*,確保,カクホ,カクホ
  36. 政府 名詞,一般,*,*,*,*,政府,セイフ,セイフ
  37. 行為 名詞,サ変接続,*,*,*,*,行為,コウイ,コーイ
  38. 戦争 名詞,サ変接続,*,*,*,*,戦争,センソウ,センソー
  39. 惨禍 名詞,一般,*,*,*,*,惨禍,サンカ,サンカ
  40. 決意 名詞,サ変接続,*,*,*,*,決意,ケツイ,ケツイ
  41. '''
これでワードクラウドに入力する準備は整ったので、あとはjanomeの出力をスペース連結したものをWordCloudに放り込めば完了となる。
ただし、WordCloudには日本語フォントが含まれていないので、フォント指定をすることになる。今回はIPAフォントを利用する。
  1. from pyparsing.helpers import Word
  2. from wordcloud import WordCloud
  3. from janome.tokenizer import Tokenizer
  4. from janome.analyzer import Analyzer
  5. from janome.tokenfilter import *
  6. txt = """
  7. 日本国民は正当に選挙された国会における代表者を通じて行動し、われらとわれらの子孫のために、諸国民と協和による成果と、わが国全土にわたって自由のもたらす恵沢を確保し、政府の行為によって再び戦争の惨禍が起こることのないようにすることを決意し、ここに主権が国民に存することを宣言し、この憲法を確定する。そもそも国政は国民の厳粛な信託によるものであって、その権威は国民に由来し、その権力は国民の代表者がこれを行使し、その福利は国民がこれを享受する。これは人類普遍の原理であり、この憲法は、かかる原理に基づくものである。われらはこれに反する一切の憲法、法令及び詔勅を排除する。
  8. 日本国民は、恒久の平和を念願し、人間相互の関係を支配する崇高な理想を深く自覚するのであって、平和を愛する諸国民の公正と信義を信頼して、われらの安全と生存を保持しようと決意した。われらは平和を維持し、専制と隷従、圧迫と偏狭を地上から永遠に除去しようと努めている国際社会において、名誉ある地位を占めたいと思う。われらは全世界の国民が、ひとしく恐怖と欠乏から免れ、平和の内に生存する権利を有することを確認する。
  9. われらは、いずれの国家も、自国のことのみに専念して他国を無視してはならないのであって、政治道徳の法則は、普遍的なものであり、この法則に従うことは、自国の主権を維持し、他国と対等関係に立とうとする各国の責務であると信ずる。
  10. 日本国民は、国家の名誉にかけて、全力をあげて崇高な理想と目的を達成することを誓う。
  11. """
  12. cloud_text = '' #出力用
  13. font_path = '/content/drive/MyDrive/tmp/ipaexm.ttf'
  14. output_path = '/content/drive/MyDrive/tmp/constitution.png'
  15. txt = txt.replace('\n', '')
  16. t = Tokenizer()
  17. filters = [CompoundNounFilter(), POSKeepFilter('名詞'), POSStopFilter(['非自立','代名詞'])]
  18. NG = ['非自立', '代名詞']
  19. analyzer = Analyzer(tokenizer=t, token_filters=filters)
  20. for token in analyzer.analyze(txt):
  21. #POSStopFilterがうまく機能しない部分があるので二重にフィルタリングする
  22. ex = token.extra[0].split(',')
  23. if all(x not in ex for x in NG):
  24. #スペース区切りで単語を連結
  25. cloud_text += ' '+ token.surface
  26. wc = WordCloud(font_path = font_path, width=1980, height=1080)
  27. wc.generate(text=cloud_text)
  28. wc.to_file(output_path)

日本国憲法前文で作成したワードクラウド
日本語ワードクラウド生成例


ひとまずこれで日本語のワードクラウドが作成できるようになったが、専門用語や流行語など、janomeのビルドイン辞書に記載されていない単語では上手くいかない。従って、辞書を生成していく必要があるが、それはまた書くことににする。




2022年10月6日木曜日

JavaScriptのPromiseを理解する

 JavaScriptの非同期処理がよくわからない、PromiseってなんぞってなったのでPromiseについてまとめてみた。

1. Promiseは何のため?

A. Promiseは非同期処理のため
これが簡潔な答えになる。
JavaScriptでは、あることをしながら別のことをするということができない。つまり、ウェブサイトのデータをダウンロードしながら画面にスピナーを表示するといったことができない。
これを解決するのが非同期処理になる。JavaScriptでは、Callbackやイベント駆動という方法がある。その新しい方法がPromiseになる。

2. Promiseのコードはどう使うか?

JavaScriptでは.then()という形で呼び出すことになる。

  1. //Fetch APIを使う場合
  2. fetch(url) //urlにアクセスして
  3. .then(response => response.json()) //responseを解釈し
  4. .then(data => console.log(data)); //データを表示する
(https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetchから引用)

このケースではFetch APIを取得し、対象のURLから情報を受け取り、JSONとして解釈して、そのデータを表示するということを行っている。
言い換えれば、.then()内に与えられる関数(ここではアロー関数)を呼び出して、fetch()の戻り値→response.json()の戻り値を順に与えて処理を行っている。


3. Promiseはどのような仕組みで動くか?

Promiseは、ある関数に一時的にPromiseというものを返し、処理を続行する。このPromiseが満たされるか、失敗するか、解決することでその関数は、処理を解決する。
Promiseを返す関数には2つのコールバック関数を登録することができる。1番目のコールバック関数が呼び出されたときにPromiseは満たされ、2番目のコールバック関数が呼び出されたときにPromiseは失敗する。
つまり、以下のようなコードで2つの関数を登録する。
  1. // funcAは処理を実行、funcBはエラーを処理
  2. fetch(url).then(funcA, funcB)
  3. //または
  4. fetch(url)
  5. .then(funcA)
  6. .catch(funcB)

そして、解決するということは、いわばPromiseを返す関数がPromiseを受け取り、処理がペンディングされている状態になる。そして、あるPromiseがPromise以外の値で満たされたとき、連鎖してPromiseが満たされ、あるPromiseが失敗すれば、同じ理由で連鎖してPromiseが失敗する。
つまり、コードで表すと以下のようになる。
  1. //HTTPリクエストを行い、最初にPromiseを返し、
  2. //リクエストが返ってきたら、Promiseはresponseオブジェクトで満たされる
  3. fetch(url)
  4. //HTTPリクエストのPromiseが満たされるまで
  5. //fetch()から返ってきたPromiseで解決される
  6. //HTTPリクエストのPromiseが満たされると
  7. //response.jsonが返すPromiseは満たされる
  8. .then(response => response.json())
  9. //response.json()の受け取ったPromiseが満たされるまで
  10. //最初のthen()が返すPromiseで解決される
  11. .then(data => displayData(data));

4. Promiseの動きを見るためのコード

Promiseは、組み込みのPromiseを返す関数以外にPromiseコンストラクタを使うことでPromiseを返す関数を作成することができる。
これを利用してミリ秒を引数として受け取り、その値を返す関数を書くと次のようになる。
  1. //durationを引数として受け取り
  2. //durationミリ秒後にPromiseをduratoinで満たす
  3. function wait(duration){
  4. return new Promise((resolve, reject)=>{
  5. setTimeout(() => {resolve(duration)}, duration);
  6. })
  7. }
  8. //start→4000→setTimeoutの順でコンソールに表示される
  9. console.log("Start");
  10. setTimeout(() => {console.log("setTimeout")}, 5000);
  11. wait(4000).then(time => {console.log(time)});
  12. //start→setTimeout→4000の順でコンソールに表示される
  13. console.log("Start");
  14. setTimeout(() => {console.log("setTimeout")}, 3000);
  15. wait(4000).then(time => {console.log(time)});

5.まとめ

Promiseは仮の値を関数に与えることで処理したことにして(処理したとみなし)、次の処理に進む。そのうえで、最初のPromiseが満たされたとき、連鎖的にPromiseが満たされて結果が返ってくる。

2022年9月28日水曜日

Pythonでヒストグラムを生成する【3/3】

 前回



前回はデータの分類をやったので今回はグラフの作成


3.グラフの作成



こういったグラフにするためには、「階級数」だけリストを作成し、それらの要素数は「最大の階級の値」する。そのうえで、各リストを最後尾から「■」で埋めて、最後に縦軸を合成すればいい。つまり、階級数が10、最大の階級の値が10であれば、10個の空白の要素を持つ10個のリストを用意すればよい。
  1. #sep_listは階級(横軸)
  2. #separatedは元データを階級別に分けたもの
  3. longest = 0
  4. for i in separated:
  5. if len(i) > longest:
  6. longest = len(i)
  7. y = longest + 1
  8. x = len(sep_list)
  9. graph = [[' ']*x for _ in range(y)]
  10. sep_list = list(map(lambda x: (map(lambda y: str(y),x)), sep_list))
  11. #sep_listは[int, int]の形なのでstrに変換
  12. graph[y-1] = list(map(lambda x:'~'.join(x), sep_list))
  13. #グラフのリスト末尾に軸を追加
  14. for i in range(x):
  15. volume = len(separated[i])
  16. for k in range(volume,0,-1): #後ろから■で埋めてく
  17. graph[y-1-k][i] = "■"

最後にグラフの縦軸を作成する。
まず、グラフの縦軸の数字を含むリスト用意する。縦軸は、グラフを含むリストの一次元目の要素数~1までをとる。ここでは次のステップの都合上0まで含むリストを「要素数」、「要素数-1」... 「0」といった形で作成する。
その後、このリストをグラフリストの一次元目の要素(リスト)の先頭に入れる形で結合する。
最後にグラフリストの[0][0]を半角スペースに置換する。


  1. #graphはグラフを格納しているリスト
  2. num = [[i] for i in range(len(graph)-1,-1,-1)]
  3. for i in range(len(graph)):
  4. graph[i] = num[i] + graph[i]
  5. self.graph[len(graph)-1][0] = ' '
最後にこれまでのコードをまとめてクラス化するとこう
  1. class histogram:
  2. """docstring-
  3. data_array -> ヒストグラムにしたいデータ(一次元リスト)
  4. sep -> ヒストグラムの階級
  5. drawメソッドで実行
  6. output_to_csvメソッドでワーキングディレクトリにcsv書き出し
  7. """
  8. def __init__(self, data_array, sep):
  9. self.data = data_array
  10. self.s = min(data_array)
  11. self.e = max(data_array)
  12. self.sep = sep
  13. self.separated = []
  14. self.sep_list = []
  15. def draw(self):
  16. self.distribute_data()
  17. self.select_list()
  18. self.draw_a_histogram()
  19. self.draw_vertical_axis()
  20. def draw_a_histogram(self):
  21. longest = 0
  22. for i in self.separated:
  23. if len(i) > longest:
  24. longest = len(i)
  25. y = longest + 1
  26. x = len(self.sep_list)
  27. self.graph = [[' ']*x for _ in range(y)]
  28. self.sep_list = list(map(lambda x: (map(lambda y: str(y),x)), self.sep_list))
  29. self.graph[y-1] = list(map(lambda x:'~'.join(x), self.sep_list))
  30. for i in range(x):
  31. volume = len(self.separated[i])
  32. for k in range(volume,0,-1):
  33. self.graph[y-1-k][i] = "■"
  34. def distribute_data(self):
  35. n = (self.e - self.s+1)//self.sep
  36. if (self.e - self.s +1)%self.sep != 0:
  37. n += 1
  38. self.separated = [[] for _ in range(n)]
  39. for i in range(n):
  40. if i == 0:
  41. self.sep_list.append([self.s, self.s + self.sep -1])
  42. else:
  43. previous = self.sep_list[i-1][1]
  44. nxt = previous + self.sep
  45. if nxt >= self.e:
  46. nxt = self.e
  47. if previous + 1 == nxt:
  48. self.sep_list.append([previous+1])
  49. else:
  50. self.sep_list.append([previous+1, nxt])
  51. def select_list(self):
  52. for i in self.data:
  53. counter = (i - self.s) // self.sep
  54. self.separated[counter].append(i)
  55. def draw_vertical_axis(self):
  56. num = [[i] for i in range(len(self.graph)-1,-1,-1)]
  57. for i in range(len(self.graph)):
  58. self.graph[i] = num[i] + self.graph[i]
  59. self.graph[len(self.graph)-1][0] = ' '
  60. def output_to_csv(self):
  61. import csv
  62. with open("./histogram.csv",encoding = "utf-8",newline='',mode="w") as f:
  63. writer = csv.writer(f)
  64. writer.writerows(self.graph)

2022年9月23日金曜日

Pythonでヒスグラムを生成する【2/3】

 前回

https://beyondthe20th.blogspot.com/2022/09/python13.html


前回は1のステップをやったので今回は2から

2.データの分類

前回の投稿で、横軸の数は、「(最大値 - 最小値+1) / 横軸の幅」で算出した。データを格納すべきリストの数と横軸の数は同じなので、同数のリストを作成する。
    	distributed_list = [[] for _ in range(n)] 
        #nは横軸の数「(最大値 - 最小値+1) / 横軸の幅」 
ここで作成したリストにデータを格納していく。
これは、そのデータが何番目のリストに格納されるかを調べるためには、「(対象のデータ-データリストの最小値)//横軸の幅」で求められる。つまり、10区切り、1~100の場合に、8は、(8-1)//10 = 0となり、0番目のリストに格納される(つまりindex = 0)。
これをPython 3のコードに直すと以下のようになる。
  1. for i in data_array:
  2. counter = (i - start) // sep
  3. distributed_list[counter].append(i)
これでdistributed_listには次のようにデータが格納された。
distributed_list = [[1番目の軸のデータ], [2番目の軸のデータ]...]
これでデータの分類が完了した。あとは、各配列に格納されているデータの個数をカウントし、縦軸を生成したうえでグラフ化、CSV出力を行えば、作成完了になる。


次回


2022年9月19日月曜日

Pythonでヒストグラムを生成する【1/3】

ヒストグラムは、品質管理の七つ道具として有名で、これを作成するライブラリやソフトウェアは、数多くある。Pythonのライブラリにもmatlabがあるし、自分で作るものでもないのだが、Pythonistaたるものライブラリに頼らず自分で作っていきたいところである。
今回は、ExcelやLibre Office Calcで表示することを前提にCSVでヒストグラムを生成するコードを書いてみた。
出来栄えはこんな感じ、完璧なヒストグラムと言っても過言ではないではないだろう。


さて、これを作るにあたっては、元データを読み込んでから、いくつかステップを踏まねばならない。今回は以下の手順で行った。
  1. 横軸の作成
  2. データの分類
  3. グラフ化
以下では順を追ってみていきたい。

1.横軸の作成

横軸の作成の作成のためには、元データから最初の項目と最後の項目を確認したうえで、いくつのリストが必要かを確認し、横軸のデータを格納するリストを用意する必要がある。従って、以下のような処理が必要になる。
  1. start = min(data_array)
  2. end = max(data_array)
  3. n = (end - start+1)// sep #(最大値-最小値+1)//横軸の幅
  4. if (end - start+1)/sep != 0:
  5. n += 1
ここでは、「(最大値 - 最小値+1) / 横軸の幅」の切り上げた値が用意すべきリストの数になることになる。つまり、1~100まで5刻みで横軸を用意したいのであれば、(100-1+1)/5 = 20、つまり20個のリストが必要となり、最後のリストには91~100までが格納される。また、1~101であれば、(101-1+1)/5 = 20.2となり、同様に21個のリストが必要となり、最後のリストには、101が格納される。
次に、軸の値を格納する必要がある。つまり、1~100まで10刻みであれば、1~10、11~20、21~30...といった形で軸の値をリストに格納していく必要がある。これは次のようになる。
  1. #sep_listは軸の値を格納するためのリスト、sepは刻みの値、nは必要なリスト数
  2. for i in range(n):
  3. if i == 0:
  4. sep_list.append([start, start + sep -1])
  5. else:
  6. previous = sep_list[i-1][1]
  7. nxt = previous + sep
  8. if nxt >= end:
  9. nxt = end
  10. if previous + 1 == nxt:
  11. sep_list.append([previous+1])
  12. else:
  13. sep_list.append([previous+1, nxt])
これは、前工程で確認したn個のリストの数だけループを回し、ループの最初で「[最小値,最小値+刻み-1,]」、それ以降は、「[前の値+1, 前の値+刻み]」といった形で処理を行っている。つまり、1~100を10刻みで軸とした場合は、最初のループで「[1, 1+10-1]」、二番目のループは「[10+1, 10+10]」という処理になっている。
これで横軸の作成が完了したので、次に収集したデータを分類して、ヒストグラム本体を作成していく。

2/3
3/3

2022年9月10日土曜日

File APIを用いたCSVファイルの入出力

VBAを使いたくないので、HTML+JavaScriptでCSVファイルを処理したいものの、File APIに関する記述がそこら中に散らばっていて、入出力をまとめた記事がないので今回作成してみた。

ファイルオブジェクトの読み書きに関してはPromiseとイベント駆動のものがあるが、今回は、わかりやすいのでイベント駆動のものを採用した。
EventListenerのコールバック関数に引数を与えるのはどうもうまくいかなかったので、グローバルオブジェクトにファイルの内容をロードする形にして読み込み→グローバルオブジェクトで読み書きとした。


  1. let loaded_csv = []; //ファイル読み込み用のグローバルオブジェクト
  2. function load(e){
  3. for (let file of e.target.files){
  4. let reader = new FileReader();
  5. reader.onload = event => {
  6. let csv = event.target.result.trim(/\r\n/).split(/\r\n/);
  7. csv = csv.map( x => x.split(','));
  8. loaded_csv.push(csv);
  9. }
  10. reader.readAsText(file);
  11. }
  12. }
  13. function create(write_array, filename){
  14. let csv_string = "";
  15. for (let i of write_array){
  16. csv_string += i.join(',');
  17. csv_string += '\r\n';
  18. }
  19. let b = new Blob([csv_string], {type: "text/csv"});
  20. let url_link = URL.createObjectURL(b);
  21. let link = document.createElement("a");
  22. link.download = filename;
  23. link.href = url_link;
  24. document.body.appendChild(link);
  25. link.click();
  26. }