こんにちは。
pythonのコードの書き方に関して質問させていただきます。
以下のようなファイルを読み込み、数え上げしているのですが
name
りんご 12
みかん 13
ぶどう 12
やさい 12
みかん 34
レンコン 34
:
:
と連なっているファイルがあるとして、左のnameそれぞれに対応する右の番号が一致した回数をカウントしたいです。私が書いたコードがこちらです。
python
1count = {} 2for line in f: 3 inputs = line.split("\t") 4 if len(inputs) != 2: 5 continue 6 7 if (count.has_key(inputs[1])): 8 count[inputs[1]] += 1 9 else: 10 count[inputs[1]] = 1 11 12sorted_count = sorted(count.items(), key=lambda x:x[1], reverse=True) 13 14for i in xrange(100): 15 print str(i + 1) + " & " + sorted_count[i][0] + " & " + str(sorted_count[i][1]) + " \ " 16 f2.write(str(i + 1) + " & " + sorted_count[i][0] + " & " + str(sorted_count[i][1]) + " \n ") 17f.close()
(ファイルの読み込みは省略しています)
このコードは、nameに関わらずカウントしてしまいます。(右の値の出現回数だけをひたすら計算)
エラーは出ずに、実行できます。
ここで、私が行いたいのはnameごとにカウントするということです。
例えば、1行目のりんごに関して右の値の出現回数を調べる。
それが終わったら、みかんに関して右の値の出現回数を調べる(この時、りんごの右の値は無視)
次は、ぶどうに関して右の値の出現回数を調べる(この時、りんご、みかんの右の値は無視)
のように、1回調べたものは次の思考では無視するということです。
その上で上のコードのように数を数えたいです。
最終的には、nameごとの右の値の出現数を合計していきたいと考えています。
わかる方いらっしゃいましたら教えていただきたいです。
よろしくお願い致します。
コメント欄に記載されているjun68yktさんの丁寧な解説のおかげでデータ数が少ない場合の処理は行うことができました(コードが多いので省略させていただきます)
しかし、データ数が増えた場合に処理が行えない(結果が出なくなってしまう)ので、データ数が多い場合でも実行できるコードの書き方があれば教えていただきたいです。
よろしくお願いいたします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答4件
0
ベストアンサー
こんにちは。
defaultdict
を使うと、与えられたキーに対応する値がない場合の初期値を指定した dict を作ることができます。これを使うことで、makaronさんのご質問にある要件は、
python
1count = defaultdict(lambda: defaultdict(int))
というdictに処理結果を保存することで、実現できると思います。
以下、上記の考えをもとに、標準入力からデータを読み込んで処理し、結果を標準出力にリストするpythonスクリプト q149275.py を作成し、これにデータファイル input.txt を読み込ませたログです。( $はシェルプロンプトです)
$ python -V
Python 2.7.10
$ cat q149275.py
python
1# coding: utf-8 2 3import sys 4from collections import defaultdict 5 6count = defaultdict(lambda: defaultdict(int)) 7 8for line in sys.stdin: 9 inputs = line.split("\t") 10 if len(inputs) != 2: 11 continue 12 count[inputs[0]][int(inputs[1])] += 1 13 14for name in count.keys(): 15 print(name) 16 for k, v in sorted(count[name].items(), key=lambda x: x[1], reverse=True): 17 print('\t%d: %d 個' % (k, v))
$ cat input.txt | python q149275.py
レンコン 1: 6 個 8: 6 個 11: 6 個 3: 4 個 7: 4 個 10: 4 個 19: 4 個 4: 3 個 9: 3 個 13: 3 個 5: 2 個 12: 2 個 14: 2 個 15: 2 個 16: 2 個 17: 2 個 18: 2 個 2: 1 個 6: 1 個 りんご 14: 6 個 7: 5 個 16: 5 個 17: 5 個 1: 4 個 2: 4 個 6: 4 個 15: 4 個 3: 3 個 11: 3 個 19: 3 個 4: 2 個 5: 2 個 8: 2 個 9: 2 個 10: 2 個 12: 2 個 13: 2 個 18: 1 個 20: 1 個 みかん 13: 7 個 16: 7 個 2: 6 個 6: 6 個 19: 6 個 12: 4 個 17: 4 個 1: 3 個 3: 3 個 4: 3 個 18: 3 個 5: 2 個 7: 2 個 10: 2 個 11: 2 個 20: 2 個 9: 1 個 14: 1 個 15: 1 個 ぶどう 20: 11 個 2: 5 個 15: 5 個 3: 4 個 5: 4 個 11: 4 個 18: 4 個 7: 3 個 10: 3 個 13: 3 個 14: 3 個 1: 2 個 4: 2 個 6: 2 個 9: 2 個 17: 2 個 12: 1 個 16: 1 個 19: 1 個 やさい 4: 5 個 8: 4 個 9: 4 個 10: 4 個 13: 4 個 18: 4 個 1: 3 個 3: 3 個 7: 3 個 16: 3 個 17: 3 個 5: 2 個 6: 2 個 11: 2 個 15: 2 個 20: 2 個 2: 1 個 12: 1 個
$
なお、上記の入力で使用した input.txt は以下です。(データが300行あります)
text
1name 2レンコン 1 3りんご 17 4ぶどう 11 5ぶどう 2 6ぶどう 16 7やさい 1 8りんご 17 9みかん 4 10みかん 2 11りんご 4 12ぶどう 20 13レンコン 12 14(・・・以下略)
上記の q149275.py の入力元、および結果の出力先、出力形式などは、質問者様の都合にあわせて修正いただければと思います。
以上、参考になれば幸いです。
追記(2018/10/5)
こんにちは。コメントから頂きました要件から、以下の 4 3点のPythonスクリプト
- count.py
- data_gen.py
- make_map.py
mod.py( https://git.io/fxOtN にて削除 )
を作成しました。これらは以下のレポジトリより取得できますので、git clone するかダウンロードしてください。
https://github.com/jun68ykt/q149275
以下、これらのスクリプトで行うことを順を追って説明します。
1. 準備
data_gen.py
,make_map.py
,count.py
には、chmod +x
で実行権限を与えます。
- また、これら3つのソースの shebang は、
#!/usr/bin/env python3
となっており、 python3 を使う前提になっていますが、これは質問者様の環境に合わせて適宜、修正をお願い致します。
- これら3つの Pythonファイルのうち、実質的な数え上げを行っているのは、
count.py
に含まれる関数count
です。
- また、以下の説明では環境変数
PATH
にカレントディレクトリ.
が含まれていることを前提にしています。
2. テストデータの作成
以下のコマンドで、data.txt
にテストデータのファイルが作成されます。
shell
1$ data_gen.py > data.txt
data.txt
は以下のようなものになります。
text
1# names: ['A', 'B', 'C', 'D', 'E'] 2# num_codes: 10 3# num_data: 20 4 5D X-07 6B X-05 7B X-01 8B X-03 9B X-06 10C X-02 11B X-08 12C X-04 13B X-04 14C X-08 15A X-08 16D X-08 17C X-03 18D X-09 19B X-02 20D X-10 21C X-07 22E X-04 23D X-03 24C X-09
上記テストデータの補足説明です。
names
はデータ行の1カラム目に出現する文字列の配列です。- データ行の2カラム目の文字列を便宜上、コード(code)と呼ぶことにしています。
data_gen.py
で生成されるコードはX-[0-9]{2}
という形式になります。num_codes: 10
は、コードの数字部分が01
から10
を取り得ることを表します。num_data
は生成されるデータ行の数です。
names
の数と num_codes
は、それぞれ、起動時の第1、第2引数で指定できます。たとえば
shell
1$ data_gen.py 9 6 > data.txt
とすると、data.txt
の冒頭は以下のようになります。
txt
1# names: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] 2# num_codes: 6 3# num_data: 21 4 5A X-03 6A X-06 7H X-01 8(...以下略)
なお、 num_data
は、
python
1num_data = int(len(names) * num_codes / 2.5)
としています。
3. マップファイルの生成
上記 2. で作成したデータファイル data.txt
を読み込んで、シンボルテーブルと、各シンボルの出現した行インデクスを含むファイルを生成します。生成されるファイルを説明の便宜上、マップファイルといいます。マップファイルの形式は json になります。
以下のようにパイプとリダイレクトを使って、マップファイルを生成します。
shell
1$ cat data.txt | make_map.py > data.map.json
data.map.json
は、以下のようなものになります。
json
1{ 2 "symbols": ["A", "X-03", "X-06", "H", "X-01", "I", "X-04", "E", "F", "X-05", "C", "G", "X-02", "D", "B"], 3 "data": [ 4 [0, 1], 5 [0, 2], 6 [3, 4], 7 [3, 2], 8 [5, 2], 9 [3, 6], 10 [7, 1], 11 [8, 9], 12 [10, 9], 13 [8, 1], 14 [11, 12], 15 [8, 6], 16 [3, 12], 17 [13, 1], 18 [0, 12], 19 [11, 1], 20 [8, 12], 21 [10, 4], 22 [10, 2], 23 [14, 2], 24 [8, 2] 25 ], 26 "indexes": [ 27 [0, 1, 14], 28 [0, 6, 9, 13, 15], 29 [1, 3, 4, 18, 19, 20], 30 [2, 3, 5, 12], 31 [2, 17], 32 [4], 33 [5, 11], 34 [6], 35 [7, 9, 11, 16, 20], 36 [7, 8], 37 [8, 17, 18], 38 [10, 15], 39 [10, 12, 14, 16], 40 [13], 41 [19] 42 ] 43}
上記のマップファイルの説明です。
"symbols"
は、 入力されたdata.txt
のデータ行に出現する、すべての name と code の文字列を出現順に並べた配列です。"data"
は、入力されたdata.txt
のデータ行を構成する name と code を、
[name_symbol_index, code_symbol_index]
という、長さ2の配列に置き換え、この長さ2の配列を要素とする配列です。
"indexes"
は、"symbols"
の各要素の文字列が出現するデータ行のインデクスを昇順に並べた配列を要素とする配列です。- たとえば上記の例では、
"indexes"
の先頭要素は[0, 1, 14]
ですが、これは、symbols[0]
である文字列"A"
が、"data"
配列のインデクス 0, 1, 14 すなわち、元のデータファイルのデータブロックの中の1,2,15 行目に出現することを表しています。
4. 数えあげ
上記 3. で作成した data.map.json
を読み込んで、各nameについての数えあげを行った結果を得るには以下のようにします。
shell
1$ cat data.map.json | count.py
上記により、以下のように、各 name を数え上げた個数が標準出力に表示されます。
A: 13
B: 2
C: 5
D: 2
E: 4
F: 8
G: 5
H: 12
I: 4
使い方の説明は以上です。
テストデータのファイル data.txt
を data_gen.py
で作成せずに、以下
txt
1りんご 12 2みかん 34 3ぶどう 56 4りんご 34 5やさい 34 6豆 12
のように作成すると、これの数え上げの結果は以下となります。
shell
1$ cat data.txt | make_map.py | count.py 2ぶどう: 1 3みかん: 3 4やさい: 1 5りんご: 4 6豆: 1 7
追記
こんにちは。
その後、考えてみましたが、約2000万という個数のデータを扱うのであれば、データの元にするはテキストファイルでよいとしても、数え上げのような分析をするために、いったんデータベースに入れるのがよいと思います。
例えば仮にデータベースをMySQLだとして、以下のようなテーブルを作ります。扱う数も大きいので、DECIMALを使って最大桁数の65を指定しています。
カラム | 型 | 内容 | 主キー |
---|---|---|---|
id | INT | 行インデクス | ○ |
name | DECIMAL(65,0) | 各行の左側の数 | |
code | DECIMAL(65,0) | 各行の右側の数 |
データファイルの10行目(行インデクス=9) が以下
114760239 781870726160588800
であったとき、このテーブルは以下のようなレコードを持ちます。
id | name | code |
---|---|---|
9 | 114760239 | 781870726160588800 |
このようにデータファイルの内容を、データベースのテーブルとして移すことができれば、数え上げを行うロジックにSQLを使うことができます。
投稿2018/09/30 05:42
編集2018/11/21 22:28総合スコア9058
0
考えてみました。
動作の説明はコメントとしてコードに記載しているので省略します。
提示されているりんご~豆のテストデータにて結果は正しかったので、自信ありませんが大丈夫と思います。多分。
ポイントとしては、行毎にファイルを読み込むことでメモリを節約しています。
Python
1import time 2# コード、名称毎に数え上げ 3 4dic_cnt = {}# コード毎、名称毎に個数を保持する2重辞書 5 # {コード:{名称:個数, ...}, ...} 6prev = time.time() 7with open('data.csv','r') as f: 8 line = f.readline().strip() 9 while line: 10 line = line.split() 11 name,code = line[0],line[1] 12 line = f.readline().strip() 13 14 # 始めて出現したコード -> 登録 15 if code not in dic_cnt: 16 dic_cnt[code] = {} 17 18 # 初めて出現した名称 -> 登録 19 if name not in dic_cnt[code]: 20 dic_cnt[code][name] = 0 21 22 # 既存の名前の個数をインクリメント 23 for name,count in dic_cnt[code].items(): 24 dic_cnt[code][name] += 1 25 26print('counting done.{:.2f}sec'.format(time.time()-prev)) 27prev = time.time() 28 29# 名称毎に集計 30dic_ret = {} 31for v in dic_cnt.values(): 32 for name,cnt in v.items(): 33 if name not in dic_ret: 34 dic_ret[name] = 0 35 dic_ret[name] += cnt 36 37# 結果ファイル出力 38with open('ret.csv','w') as f: 39 for name,cnt in dic_ret.items(): 40 f.write('{} {}\n'.format(name,cnt)) 41 42print('sum up done.{:.2f}sec'.format(time.time()-prev)) 43prev = time.time()
テストデータでの検証結果
以下、行数2千万、名称種類30万、コード種類20万で試した結果です。
Corei7搭載機で約5分。まあ我慢できるレベルかと。
ちなみにコード種類数が少なくなると計算量は急激に増加しますのでご注意を。
その際はデータベースを利用するなど別の手法を考えるべきです。
以下、実行結果
counting done.310.84sec sum up done.15.48sec
以下、入力データ data.csv 約420MB
name27454 code70170 name45711 code106756 name139751 code28233 :
以下、結果データ ret.csv 約5MB
name210822 4235 name52534 3304 name10758 3314 :
テストデータ作成コード
なお、サンプルデータは以下のコードで作成しました。
Python
1# テストデータ作成 2import random 3random.seed(123) # 乱数固定 4ROW,NAME,CODE = 20000000,300000,200000 # 行数、名称数、コード数 5dup = {} # # 名称+コード重複行チェック用 6prev = time.time() 7with open('data.csv','w') as f: 8 for _ in range(ROW): 9 name = 'name{}'.format(random.randint(1,NAME)) 10 code = 'code{}'.format(random.randint(1,CODE)) 11 if name+code not in dup: # 名称+コード重複行は存在しない仕様 12 f.write('{} {}\n'.format(name,code)) 13 dup[name+code] = 1 14 15print('create data done.{:.2f}sec'.format(time.time()-prev)) # create data done.88.37sec 16prev = time.time() 17print(ROW,NAME,CODE)
データ分析コード
ついでに、データファイルの分析をおこなうコードを作成しました。
行数や名称,コード(種類)数、名称,コード文字長の最小、最大、平均を出力します。
Python
1# データの分析 2 3# 文字列の最小、最大、合計を保持 4def get_len_info( s, mm): 5 s_len = len(s) 6 mm[2] += s_len 7 8 if s_len < mm[0]: 9 mm[0] = s_len 10 if s_len > mm[1]: 11 mm[1] = s_len 12 13# 行 0=名称, 1=コード 14# 列 0=最小, 1=最大, 2=合計 15len_info = [[99999,-1,0],[99999,-1,0]] 16 17row_cnt,name_set,code_set = 0,set(),set() 18with open('data.csv','r') as f: 19 line = f.readline().strip() 20 while line: 21 line = line.split() 22 name,code = line[0],line[1] 23 line = f.readline().strip() 24 25 name_set.add(name) 26 code_set.add(code) 27 28 # 文字列長の最小、最大、合計を保持 29 get_len_info(name,len_info[0]) 30 get_len_info(code,len_info[1]) 31 32 row_cnt += 1 33 34name_cnt,code_cnt = len(name_set),len(code_set) 35 36print('行数[{}] 名称(種類)数[{}] コード(種類)数[{}]'.format(row_cnt,name_cnt,code_cnt)) 37print('名称長 最小[{:.0f}] 最大[{:.0f}] 平均[{:.2f}]'.format( len_info[0][0], len_info[0][1], len_info[0][2]/row_cnt)) 38print('コド長 最小[{:.0f}] 最大[{:.0f}] 平均[{:.2f}]'.format( len_info[1][0], len_info[1][1], len_info[1][2]/row_cnt)) 39""" 40行数[19996722] 名称(種類)数[300000] コード(種類)数[200000] 41名称長 最小[5] 最大[10] 平均[9.63] 42コド長 最小[5] 最大[10] 平均[9.44] 43"""
投稿2018/11/18 13:42
編集2018/11/19 01:59総合スコア38233
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/11/18 14:22
2018/11/18 14:29
2018/11/18 14:32
2018/11/18 14:39
2018/11/18 14:41
2018/11/18 14:46
2018/11/18 14:49
2018/11/18 14:52
2018/11/18 14:53
2018/11/18 15:09
2018/11/18 15:21
2018/11/19 01:19
2018/11/19 02:03
2018/11/20 01:02
2018/11/20 01:03
0
jun68yktさんとのコメントでのやり取りを眺めて、コードを起こしてみました。
python
1from collections import defaultdict 2 3txt = """りんご 12 4みかん 34 5ぶどう 56 6りんご 34 7やさい 34 8豆 12 9""" 10 11data = [line.split() for line in txt.splitlines()] 12 13result_dict = defaultdict(int) 14 15for i, (key, val) in enumerate(data): 16 for k, v in data[i:]: 17 if v == val: 18 result_dict[key] += 1 19 20for k, v in sorted(result_dict.items(), key=lambda x:x[1], reverse=True): 21 print(k, v) 22""" => 23りんご 4 24みかん 3 25ぶどう 1 26やさい 1 27豆 1 28""" 29
件数があると計算量的にきついかもしれないので、改良した方が良いかも。
投稿2018/09/30 23:26
総合スコア30933
0
name
りんご 12
りんご 13
りんご 12
りんご 12
というファイルの場合、「りんご 12」と「りんご 13」で別々に出現回数を調べるという話だと思います。
なので、
count[("りんご", "12")] = 3
count[("りんご", "13")] = 1
となるように作るのはどうでしょう。ディクショナリのkeyはリストはダメなので、タプルにします。
Python
1count = {} 2for line in f: 3 inputs = tuple(line.split("\t")) 4 if len(inputs) != 2: 5 continue 6 7 if (count.has_key(inputs)): 8 count[inputs] += 1 9 else: 10 count[inputs] = 1 11 12sorted_count = sorted(count.items(), key=lambda x:x[1], reverse=True)
投稿2018/09/30 05:41
総合スコア312
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/09/30 07:04
2018/09/30 14:20
2018/10/01 07:22
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/09/30 07:19
2018/09/30 07:49
2018/09/30 08:03 編集
2018/09/30 08:30 編集
2018/10/01 04:55
2018/10/04 20:17
2018/10/04 20:57
2018/10/05 05:49 編集
2018/10/05 06:04
2018/10/05 10:54 編集
2018/10/05 13:39
2018/10/05 18:08
2018/10/06 01:15
2018/10/06 02:31 編集
2018/10/06 02:22
2018/10/06 02:43
2018/10/06 03:08
2018/10/06 03:20
2018/10/06 03:50
2018/10/06 03:55
2018/11/17 09:42
2018/11/17 09:48
2018/11/17 23:18
2018/11/18 00:30
2018/11/18 00:42
2018/11/18 01:46
2018/11/18 01:47
2018/11/18 01:48
2018/11/18 01:54
2018/11/18 08:33
2018/11/18 11:12
2018/11/18 11:14
2018/11/18 11:17
2018/11/18 11:20
2018/11/18 12:13 編集
2018/11/18 12:14
2018/11/18 12:15
2018/11/18 12:18
2018/11/18 13:45
2018/11/18 14:01
2018/11/18 14:04
2018/11/18 14:07
2018/11/21 22:37 編集
2018/11/27 06:23
2018/11/27 06:24