2022年9月28日水曜日

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

 前回



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


3.グラフの作成



こういったグラフにするためには、「階級数」だけリストを作成し、それらの要素数は「最大の階級の値」する。そのうえで、各リストを最後尾から「■」で埋めて、最後に縦軸を合成すればいい。つまり、階級数が10、最大の階級の値が10であれば、10個の空白の要素を持つ10個のリストを用意すればよい。
#sep_listは階級(横軸)
#separatedは元データを階級別に分けたもの

longest = 0
for i in separated:
    if len(i) > longest:
        longest = len(i)
y = longest + 1 
x = len(sep_list)
graph = [[' ']*x for _ in range(y)]
sep_list = list(map(lambda x: (map(lambda y: str(y),x)), sep_list))
 	#sep_listは[int, int]の形なのでstrに変換
graph[y-1] = list(map(lambda x:'~'.join(x), sep_list))
	#グラフのリスト末尾に軸を追加
for i in range(x):
    volume = len(separated[i])
    for k in range(volume,0,-1): #後ろから■で埋めてく
        graph[y-1-k][i] = "■"
    

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


#graphはグラフを格納しているリスト
num = [[i] for i in range(len(graph)-1,-1,-1)]
for i in range(len(graph)):
	graph[i] = num[i] + graph[i]
self.graph[len(graph)-1][0] = ' '
最後にこれまでのコードをまとめてクラス化するとこう
class histogram:
    """docstring-
        data_array -> ヒストグラムにしたいデータ(一次元リスト)
        sep -> ヒストグラムの階級    
        drawメソッドで実行
        output_to_csvメソッドでワーキングディレクトリにcsv書き出し
    """
    def __init__(self, data_array, sep):
        self.data = data_array
        self.s = min(data_array)
        self.e = max(data_array)
        self.sep = sep
        self.separated = []
        self.sep_list = []

    def draw(self):
        self.distribute_data()
        self.select_list()
        self.draw_a_histogram()
        self.draw_vertical_axis()

    def draw_a_histogram(self):
        longest = 0
        for i in self.separated:
            if len(i) > longest:
                longest = len(i)
        y = longest + 1
        x = len(self.sep_list)
        self.graph = [[' ']*x for _ in range(y)]
        self.sep_list = list(map(lambda x: (map(lambda y: str(y),x)), self.sep_list))
        self.graph[y-1] = list(map(lambda x:'~'.join(x), self.sep_list))
        for i in range(x):
            volume = len(self.separated[i])
            for k in range(volume,0,-1):
                self.graph[y-1-k][i] = "■"
        
    def distribute_data(self):
        n = (self.e - self.s+1)//self.sep
        if (self.e - self.s +1)%self.sep != 0:
            n += 1
        self.separated = [[] for _ in range(n)]
        for i in range(n):
            if i == 0:
                self.sep_list.append([self.s, self.s + self.sep -1])
            else:
                previous = self.sep_list[i-1][1]
                nxt = previous + self.sep
                if nxt >= self.e:
                    nxt = self.e
                if previous + 1 == nxt:
                    self.sep_list.append([previous+1])
                else:
                    self.sep_list.append([previous+1, nxt])

    def select_list(self):
        for i in self.data:
            counter = (i - self.s) // self.sep
            self.separated[counter].append(i)

    def draw_vertical_axis(self):
        num = [[i] for i in range(len(self.graph)-1,-1,-1)]
        for i in range(len(self.graph)):
            self.graph[i] = num[i] + self.graph[i]
        self.graph[len(self.graph)-1][0] = ' '
        
    def output_to_csv(self):
        import csv
        with open("./histogram.csv",encoding = "utf-8",newline='',mode="w") as f:
            writer = csv.writer(f)
            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のコードに直すと以下のようになる。
    for i in data_array:
    	counter = (i - start) // sep
        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.横軸の作成

横軸の作成の作成のためには、元データから最初の項目と最後の項目を確認したうえで、いくつのリストが必要かを確認し、横軸のデータを格納するリストを用意する必要がある。従って、以下のような処理が必要になる。
start = min(data_array)
end = max(data_array)
n = (end - start+1)// sep #(最大値-最小値+1)//横軸の幅
if (end - start+1)/sep != 0: 
	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...といった形で軸の値をリストに格納していく必要がある。これは次のようになる。
#sep_listは軸の値を格納するためのリスト、sepは刻みの値、nは必要なリスト数
for i in range(n):
	if i == 0:
		sep_list.append([start, start + sep -1])
    else:
		previous = sep_list[i-1][1]
		nxt = previous + sep
		if nxt >= end:
			nxt = end
			if previous + 1 == nxt:
				sep_list.append([previous+1])
			else:
				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のコールバック関数に引数を与えるのはどうもうまくいかなかったので、グローバルオブジェクトにファイルの内容をロードする形にして読み込み→グローバルオブジェクトで読み書きとした。


    
let loaded_csv = []; //ファイル読み込み用のグローバルオブジェクト
function load(e){
	for (let file of e.target.files){
    	let reader = new FileReader();
        reader.onload = event => {
        	let csv = event.target.result.trim(/\r\n/).split(/\r\n/);
            csv = csv.map( x => x.split(','));
            loaded_csv.push(csv);
            }
            reader.readAsText(file);
        }
}

function create(write_array, filename){
	let csv_string = "";
    for (let i of write_array){
    	csv_string += i.join(',');
        csv_string += '\r\n';
    	}
	let b = new Blob([csv_string], {type: "text/csv"});
    let url_link = URL.createObjectURL(b);
    let link = document.createElement("a");
    link.download = filename;
    link.href = url_link;
    document.body.appendChild(link);
    link.click();
}