質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

python 数え上げ

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,711

makaron

score 11

こんにちは。
pythonのコードの書き方に関して質問させていただきます。

以下のようなファイルを読み込み、数え上げしているのですが

name
りんご 12
みかん 13
ぶどう 12
やさい 12
みかん 34
レンコン 34
:
:

と連なっているファイルがあるとして、左のnameそれぞれに対応する右の番号が一致した回数をカウントしたいです。私が書いたコードがこちらです。

count = {}
for line in f:
  inputs = line.split("\t")
  if len(inputs) != 2:
    continue

  if (count.has_key(inputs[1])):
    count[inputs[1]] += 1
  else:
    count[inputs[1]] = 1

sorted_count = sorted(count.items(), key=lambda x:x[1], reverse=True)

for i in xrange(100):
 print  str(i + 1) + " & " + sorted_count[i][0] + " & " + str(sorted_count[i][1]) + " \\ "
 f2.write(str(i + 1) + " & " + sorted_count[i][0] + " & " + str(sorted_count[i][1]) + " \n ")
f.close()

(ファイルの読み込みは省略しています)
このコードは、nameに関わらずカウントしてしまいます。(右の値の出現回数だけをひたすら計算)
エラーは出ずに、実行できます。

ここで、私が行いたいのはnameごとにカウントするということです。
例えば、1行目のりんごに関して右の値の出現回数を調べる。
それが終わったら、みかんに関して右の値の出現回数を調べる(この時、りんごの右の値は無視)
次は、ぶどうに関して右の値の出現回数を調べる(この時、りんご、みかんの右の値は無視)
のように、1回調べたものは次の思考では無視するということです。
その上で上のコードのように数を数えたいです。
最終的には、nameごとの右の値の出現数を合計していきたいと考えています。

わかる方いらっしゃいましたら教えていただきたいです。
よろしくお願い致します。

コメント欄に記載されているjun68yktさんの丁寧な解説のおかげでデータ数が少ない場合の処理は行うことができました(コードが多いので省略させていただきます)
しかし、データ数が増えた場合に処理が行えない(結果が出なくなってしまう)ので、データ数が多い場合でも実行できるコードの書き方があれば教えていただきたいです。
よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • 退会済みユーザー

    2018/11/22 07:46

    複数のユーザーから「やってほしいことだけを記載した丸投げの質問」という意見がありました
    「質問を編集する」ボタンから編集を行い、調査したこと・試したことを記入していただくと、回答が得られやすくなります。

回答 4

checkベストアンサー

+2

こんにちは。

defaultdict を使うと、与えられたキーに対応する値がない場合の初期値を指定した dict を作ることができます。これを使うことで、makaronさんのご質問にある要件は、

count = defaultdict(lambda: defaultdict(int))

というdictに処理結果を保存することで、実現できると思います。

以下、上記の考えをもとに、標準入力からデータを読み込んで処理し、結果を標準出力にリストするpythonスクリプト q149275.py を作成し、これにデータファイル input.txt を読み込ませたログです。( $はシェルプロンプトです)

python -V
Python 2.7.10
cat q149275.py

# coding: utf-8

import sys
from collections import defaultdict

count = defaultdict(lambda: defaultdict(int))

for line in sys.stdin:
    inputs = line.split("\t")
    if len(inputs) != 2:
        continue
    count[inputs[0]][int(inputs[1])] += 1

for name in count.keys():
    print(name)
    for k, v in sorted(count[name].items(), key=lambda x: x[1], reverse=True):
        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行あります)

name
レンコン    1
りんご    17
ぶどう    11
ぶどう    2
ぶどう    16
やさい    1
りんご    17
みかん    4
みかん    2
りんご    4
ぶどう    20
レンコン    12
(・・・以下略)

上記の q149275.py の入力元、および結果の出力先、出力形式などは、質問者様の都合にあわせて修正いただければと思います。

以上、参考になれば幸いです。

 追記(2018/10/5)

こんにちは。コメントから頂きました要件から、以下の  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.pymake_map.pycount.py には、 chmod +x で実行権限を与えます。
      
  • また、これら3つのソースの shebang は、#!/usr/bin/env python3 となっており、 python3 を使う前提になっていますが、これは質問者様の環境に合わせて適宜、修正をお願い致します。
      
  • これら3つの Pythonファイルのうち、実質的な数え上げを行っているのは、count.pyに含まれる関数 countです。 
      
  • また、以下の説明では環境変数 PATH にカレントディレクトリ . が含まれていることを前提にしています。
      

 2. テストデータの作成

以下のコマンドで、data.txt にテストデータのファイルが作成されます。

$ data_gen.py > data.txt

data.txt は以下のようなものになります。

# names: ['A', 'B', 'C', 'D', 'E']
# num_codes: 10
# num_data: 20

D    X-07
B    X-05
B    X-01
B    X-03
B    X-06
C    X-02
B    X-08
C    X-04
B    X-04
C    X-08
A    X-08
D    X-08
C    X-03
D    X-09
B    X-02
D    X-10
C    X-07
E    X-04
D    X-03
C    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引数で指定できます。たとえば

$ data_gen.py 9 6 > data.txt

とすると、data.txtの冒頭は以下のようになります。

# names: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
# num_codes: 6
# num_data: 21

A    X-03
A    X-06
H    X-01
(...以下略)

なお、 num_data は、

num_data = int(len(names) * num_codes / 2.5)


としています。

 3. マップファイルの生成

上記 2. で作成したデータファイル data.txt を読み込んで、シンボルテーブルと、各シンボルの出現した行インデクスを含むファイルを生成します。生成されるファイルを説明の便宜上、マップファイルといいます。マップファイルの形式は json になります。

以下のようにパイプとリダイレクトを使って、マップファイルを生成します。

$ cat data.txt | make_map.py > data.map.json

data.map.json は、以下のようなものになります。

{
  "symbols": ["A", "X-03", "X-06", "H", "X-01", "I", "X-04", "E", "F", "X-05", "C", "G", "X-02", "D", "B"],
  "data": [
    [0, 1],
    [0, 2],
    [3, 4],
    [3, 2],
    [5, 2],
    [3, 6],
    [7, 1],
    [8, 9],
    [10, 9],
    [8, 1],
    [11, 12],
    [8, 6],
    [3, 12],
    [13, 1],
    [0, 12],
    [11, 1],
    [8, 12],
    [10, 4],
    [10, 2],
    [14, 2],
    [8, 2]
  ],
  "indexes": [
    [0, 1, 14],
    [0, 6, 9, 13, 15],
    [1, 3, 4, 18, 19, 20],
    [2, 3, 5, 12],
    [2, 17],
    [4],
    [5, 11],
    [6],
    [7, 9, 11, 16, 20],
    [7, 8],
    [8, 17, 18],
    [10, 15],
    [10, 12, 14, 16],
    [13],
    [19]
  ]
}

上記のマップファイルの説明です。

  • "symbols" は、 入力された data.txtのデータ行に出現する、すべての name と code の文字列を出現順に並べた配列です。
  • "data" は、入力された data.txtのデータ行を構成する name と code を、
    [name_symbol_indexcode_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についての数えあげを行った結果を得るには以下のようにします。

$ 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 で作成せずに、以下

りんご 12
みかん 34
ぶどう 56
りんご 34
やさい 34
豆   12

のように作成すると、これの数え上げの結果は以下となります。

$ cat data.txt | make_map.py | count.py
ぶどう: 1
みかん: 3
やさい: 1
りんご: 4
豆: 1

 追記

こんにちは。

その後、考えてみましたが、約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/11/22 07:36 編集

    こんにちは。
    先ほど追記したようにテキストファイルの内容をデータベースに投入することができれば、数え上げのロジック自体は、部分的にSQLを使うということを除いて、大筋のロジック自体は初めに回答したものとそれほど大きな違いはなく書けると思います。

    次のステップに進む前に、
    何らかのデータベースを用意して追記に書いたようなテーブルを作成し、データファイルの内容を投入することはできますか?
    ということを確認させて頂きたいです。

    キャンセル

  • 2018/11/27 15:23

    確認が遅くなってしまい申し訳ありません。
    はい、データベースを使用することは可能です!

    キャンセル

  • 2018/11/27 15:24

    しかし、データベースに関しては大まかな知識しかないので、詳しい使い方やコードの書き方はあまりわかっておりません。

    キャンセル

+1

jun68yktさんとのコメントでのやり取りを眺めて、コードを起こしてみました。

from collections import defaultdict

txt = """りんご 12
みかん 34
ぶどう 56
りんご 34
やさい 34
豆   12
"""

data = [line.split() for line in txt.splitlines()]

result_dict = defaultdict(int)

for i, (key, val) in enumerate(data):
    for k, v in data[i:]:
        if v == val:
            result_dict[key] += 1

for k, v in sorted(result_dict.items(), key=lambda x:x[1], reverse=True):
    print(k, v)
""" =>
りんご 4
みかん 3
ぶどう 1
やさい 1
豆 1
"""

件数があると計算量的にきついかもしれないので、改良した方が良いかも。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/01 14:03 編集

    hayataka2049さん
    ご回答ありがとうございます。
    ファイルの1部を引っ張り、実行してみたところ正しい結果を出すことができました。ありがとうございます。
    しかし、データの中身が多く可能であればファイルの中身を読み込んで同様の処理を行いたいのですが、その場合3行目のdata=の部分を変更するのが良いでしょうか?
    ご回答ありがとうございました。
    こちらについても可能でしたら教えていただけたら幸いです。

    キャンセル

+1

考えてみました。
動作の説明はコメントとしてコードに記載しているので省略します。
提示されているりんご~豆のテストデータにて結果は正しかったので、自信ありませんが大丈夫と思います。多分。
ポイントとしては、行毎にファイルを読み込むことでメモリを節約しています。

import time
# コード、名称毎に数え上げ

dic_cnt = {}# コード毎、名称毎に個数を保持する2重辞書
            # {コード:{名称:個数, ...}, ...}
prev = time.time()
with open('data.csv','r') as f:
    line = f.readline().strip()
    while line:
        line = line.split()
        name,code = line[0],line[1]
        line = f.readline().strip()

        # 始めて出現したコード -> 登録
        if code not in dic_cnt:
            dic_cnt[code] = {}

        # 初めて出現した名称 -> 登録
        if name not in dic_cnt[code]:
            dic_cnt[code][name] = 0

        # 既存の名前の個数をインクリメント
        for name,count in dic_cnt[code].items():
            dic_cnt[code][name] += 1

print('counting done.{:.2f}sec'.format(time.time()-prev))
prev = time.time()

# 名称毎に集計
dic_ret = {}
for v in dic_cnt.values():
    for name,cnt in v.items():
        if name not in dic_ret:
            dic_ret[name] = 0
        dic_ret[name] += cnt

# 結果ファイル出力
with open('ret.csv','w') as f:
    for name,cnt in dic_ret.items():
        f.write('{} {}\n'.format(name,cnt))

print('sum up done.{:.2f}sec'.format(time.time()-prev))
prev = 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

テストデータ作成コード

なお、サンプルデータは以下のコードで作成しました。

# テストデータ作成
import random
random.seed(123) # 乱数固定
ROW,NAME,CODE = 20000000,300000,200000 # 行数、名称数、コード数
dup = {} # # 名称+コード重複行チェック用
prev = time.time()
with open('data.csv','w') as f:
    for _ in range(ROW):
        name = 'name{}'.format(random.randint(1,NAME))
        code = 'code{}'.format(random.randint(1,CODE))
        if name+code not in dup: # 名称+コード重複行は存在しない仕様
            f.write('{} {}\n'.format(name,code))
            dup[name+code] = 1

print('create data done.{:.2f}sec'.format(time.time()-prev)) # create data done.88.37sec
prev = time.time()
print(ROW,NAME,CODE)

データ分析コード

ついでに、データファイルの分析をおこなうコードを作成しました。
行数や名称,コード(種類)数、名称,コード文字長の最小、最大、平均を出力します。

# データの分析

# 文字列の最小、最大、合計を保持
def get_len_info( s, mm):
    s_len = len(s)
    mm[2] += s_len

    if s_len < mm[0]:
        mm[0] = s_len
    if s_len > mm[1]:
        mm[1] = s_len

# 行 0=名称, 1=コード
# 列 0=最小, 1=最大, 2=合計
len_info = [[99999,-1,0],[99999,-1,0]]

row_cnt,name_set,code_set = 0,set(),set()
with open('data.csv','r') as f:
    line = f.readline().strip()
    while line:
        line = line.split()
        name,code = line[0],line[1]
        line = f.readline().strip()

        name_set.add(name)
        code_set.add(code)

        # 文字列長の最小、最大、合計を保持
        get_len_info(name,len_info[0])
        get_len_info(code,len_info[1])

        row_cnt += 1

name_cnt,code_cnt = len(name_set),len(code_set)

print('行数[{}] 名称(種類)数[{}] コード(種類)数[{}]'.format(row_cnt,name_cnt,code_cnt))
print('名称長 最小[{:.0f}] 最大[{:.0f}] 平均[{:.2f}]'.format( len_info[0][0], len_info[0][1], len_info[0][2]/row_cnt))
print('コド長 最小[{:.0f}] 最大[{:.0f}] 平均[{:.2f}]'.format( len_info[1][0], len_info[1][1], len_info[1][2]/row_cnt))
"""
行数[19996722] 名称(種類)数[300000] コード(種類)数[200000]
名称長 最小[5] 最大[10] 平均[9.63]
コド長 最小[5] 最大[10] 平均[9.44]
"""

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/19 11:03

    データ分析コードを追記しました。
    可能であれば参考のため分析実行した結果も教えてください。

    キャンセル

  • 2018/11/20 10:02

    こんにちは。
    遅くなり申し訳ありません。
    無事、問題なく分析行うことができました!
    ありがとうございます。コードの追記もありがとうございました。
    counting done.24714.07sec
    sum up done.39.21sec
    こちらのような結果になりました。

    キャンセル

  • 2018/11/20 10:03

    データ分析コードはこれから拝見させていただきます。

    キャンセル

0

name
りんご 12
りんご 13
りんご 12
りんご 12

というファイルの場合、「りんご 12」と「りんご 13」で別々に出現回数を調べるという話だと思います。
なので、
count[("りんご", "12")] = 3
count[("りんご", "13")] = 1
となるように作るのはどうでしょう。ディクショナリのkeyはリストはダメなので、タプルにします。

count = {}
for line in f:
  inputs = tuple(line.split("\t"))
  if len(inputs) != 2:
    continue

  if (count.has_key(inputs)):
    count[inputs] += 1
  else:
    count[inputs] = 1

sorted_count = sorted(count.items(), key=lambda x:x[1], reverse=True)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/30 16:04

    お返事ありがとうございます。
    まだ、pythonの学習期間が短くタプルというものがわかっておらず、ディクショなりとの違いを教えていただきたいです。コードを実行するとエラーになってしまうのですが、このコードにタプルというものを組み合わせる感じでしょうか?

    キャンセル

  • 2018/09/30 23:20

    エラーメッセージを貼ってください

    キャンセル

  • 2018/10/01 16:22

    ご返信ありがとうございます。
    遅くなってしまい申し訳ありません。
    わたしが挿入したファイルを読み込ませるコードに原因があり、そこを直したら修正できました。
    この実行結果を別のファイルに書き込む書き方についても教えていただきたいです。以前のコードのf2.writeを色々変えてみたのですが対応のさせ方がわかりませんでした。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る