[완료]Python을 활용한 텍스트 자료 간단 통계 내기

minic3000의 이미지

안녕하세요~

요즘 파이썬을 배워서 텍스트 기반 DB에 대한 간단한 통계를 내고, 비교하는 걸 해보려고 노력중인 학생(?)입니다.

파이썬이 쉽다고 해서 처음 공부하기 시작할때는 의욕에 충만하였는데, 역시 혼자 공부하는 초보인 저에게는

쉽지않은 벽을 보여주네요..

여차저차 해서 파일을 읽고 출력하는 것까지는 목표자료 가지고 해봤는데, 어떻게 통계내고 비교한 결과를 출력할 수 있는지

앞이 깜깜해서 도움을 요청드립니다.

제가 작업하려는 것은 예를 들어,

-a.txt-
10001; 10; 20
10002; 20; 10
10103; 30; 40
20001; 40; 60
20010; 50; 10
30201; 60; 10

-b.csv-
10001, 1.25, 1.25
10002, 2.2, 2.2
10103, 1.55, 1.55
20001, 11.1, 11.1
20010, 0, 0
30201, 20.2, 20.2

이렇게 두 파일이 있습니다. 첫번째 컬럼은 일종의 ID가 되는 거구요.. 제품분류코드같은 것으로 보셔도 무방합니다.
맨 앞자리가 상위분류체계 번호고 다음 자리수로 가면서 하위분류체계를 구분하게 됩니다.
만약에 최상위 분류체계로 합을 구하겠다는 조건을 주면 a.txt 파일과 b.txt파일을 읽고 첫번째 컬럼을 확인하여
최상위 분류코드가 1인것은 1인 것끼리, 2인것은 2인 것끼리 합을 구해서 아래처럼 출력되었으면 합니다.

100000, 65, 75
200000, 101.1, 81.1
300000, 80.2, 30.2

어떤 파일은 CSV 형식이고 어떤 파일은 세미콜론으로 구분되어 혼재되어 있습니다(물론 어떤 파일이 어떤 형식인지는 규칙이 있습니다)

사실 제가 요청드린 내용이 난이도가 높은 수준인지, 혹은 매우 쉬운 수준인지 조차도 잘 모르겠습니다.
어떻게 코드를 작성해나가야 하는지 감이 오지 않아서 혹시 참고할 만한 예시가 있을까 해서 구글링도 열심히 해보았지만
여전히 길이 보이지 않더군요,,ㅜㅜ

전문가분들의 많은 조언 부탁드리겠습니다.

neocoin의 이미지

주어진 예시의 수준은 난이도가 그리 높지 않습니다. 예제 삼아 보시라고 코드를 작성했습니다.

SQL을 아시면, 그냥 저 정보를 전부 db에 담아서 SQL로 처리하는게 편할 듯 보이네요.

제가 루비가 편해서 루비 먼저 ..

ruby 1.9

require 'csv'
require 'set'
 
a = CSV.open('a.txt', 'r', :col_sep => ';').to_a
b = CSV.open('b.csv','r').to_a
 
c = (a + b).map{|i| i[0] = "#{i[0][0]}0000";i}
c.map!{|i|i.map{|j|j.to_f}}
 
 
ids = Set.new(c.map{|i|i.first})
ids.each do |i|
  data = c.select{|o| o.first == i}.transpose[1..-1]
  sums = data.map{|i|i.inject(0){|memo,o|memo += o}}
  puts (["%d"%i] << sums).join(',')
end

output 은 예제와 동일합니다.

그냥 루비 스크립트 pyton으로 옮긴겁니다.

python 2.6

import csv
 
a = csv.reader(open('a.txt','r'), delimiter=';')
b = csv.reader(open('b.csv'))
c = [i for i in a] + [i for i in b]
 
for i,v in enumerate(c):
  v[0] = "%s0000"%(v[0][0])
 
c = [[float(v) for v in i] for i in c]
ids = set([i[0] for i in c])
 
for id in ids:
  data = map(None,*filter(lambda x:x[0] == id,c))  #transpose
  sums = [sum(i) for i in data[1:]]
  print ",".join(map(str,["%d"%id] + sums))

output 은 예제와 동일합니다.

거의 비슷한 양의 코드이지만, ruby는 코드 읽기가 왼쪽에서 오른쪽으로 일관되게 읽히는 반면, python 은 내장 함수들의 사용법 때문에 왼쪽 오른쪽을 왔다갔다 하는게 그리 마음에 들지 않습니다.

저는 ruby 쓰기 전에는 python 신도였는데, python에 불만을 표현하다니.. 격세지감이네요.

더 괜찮은 코드는 다른분께 기대합니다 ;;

cedar의 이미지

Python 2.6 이상의 기능을 최대한 활용해 보았습니다.

import csv
import itertools
import collections
import heapq
 
class Point(collections.namedtuple('Point', 'x y')):   
    '''nametuple은 2.6 이상: 2.5 이하에서는 일반 클래스로 해도 됩니다.'''
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
 
    def __str__(self):
        return '%6.2f, %6.2f' % self
        # return '{0:>6.2f}, {1:>6.2f}'.format(*self)   # 2.6 이상에서 가능한 방법(1)
        # return '{self.x:>6.2f}, {self.y:>6.2f}'.format(self=self) # 2.6 이상에서 가능한 방법(2)
 
def read_csv(filename, delimeter=','):
    return ((r[0], Point(float(r[1]), float(r[2]))) 
                for r in csv.reader(open(filename), skipinitialspace=True, delimiter=delimeter))
 
def main():
    a = read_csv('a.txt', ';')
    b = read_csv('b.csv')
    merged = heapq.merge(a, b)  # 2.6 이상: a, b 모두 정렬되었을 때 사용
    for key, group in itertools.groupby(merged, lambda x : x[0][0]):
        print "{0}0000, {1}".format(key, sum((g[1] for g in group), Point(0, 0)))
 
if __name__ == '__main__':
    main()
neocoin의 이미지

행렬 연산 느낌 가지고 싶어서 transpose 넣었는데 버리고..

require 'csv'
a = CSV.open('a.txt', 'r', :col_sep => ';').to_a
b = CSV.open('b.csv','r').to_a
 
data = a+b
h = Hash.new{|h,k|h[k]=[]}
data.each do |r|
  sum = h["#{r[0][0]}0000"]
  r[1..-1].each_with_index{|v,i| sum[i] = sum[i].to_f+v.to_f}
end
h.each{|k,v| puts ([k] << v).join(',')}

csv 모듈 빼버리고..

이렇게 실행

ruby test.rb a.txt b.csv

data = ARGV.inject([]){|memo,p| memo += open(p).readlines}
           .map{|l|l.scan(/[.\d]+/).flatten}
 
h = Hash.new{|h,k|h[k]=[]}
data.each do |r|
  sum = h["#{r[0][0]}0000"]
  r[1..-1].each_with_index{|v,i| sum[i] = sum[i].to_f+v.to_f}
end
h.each{|k,v| puts ([k] << v).join(',')}

그래도 csv 있는게 더 좋아 보이네요..

비슷하게 python 도 추가 합니다.

import csv
 
a = csv.reader(open('a.txt','r'), delimiter=';')
b = csv.reader(open('b.csv'))
data = [i for i in a] + [i for i in b]
 
d = {}
for row in data:
  k = "%s0000"%(row[0][0])
  if not d.has_key(k):
    d[k] = [0 for i in range(len(row)-1)]
  d[k] = map(lambda x,y:x+float(y),d[k],row[1:])
 
for k,v in d.items():
  print k,','.join(map(str,v))
minic3000의 이미지

앗 감사합니다. 한번 테스트 해봐야겠어요~^^

minic3000의 이미지

가르쳐주신 코드를 조금 수정해서 시도해보았습니다.
테스트는 성공했습니다만 실제 적용해보니 몇가지 문제가 있었습니다.

첫째로 각 입력파일이 양이 많습니다(수천줄에서 수만줄..)
리스트나 사전으로 처리하는데 문제가 없는지요..

둘째로 float(y) (12번째줄) 부분에서 에러가 나왔습니다.
에러메세지 : TypeError: float() argument must be a string or a number

입력되는 숫자가 10.12345E+02 이런 형식인데, 혹시 이것 때문인지요..

테스트를 성공해서 엄청 기뻐했는데, 실제는 역시 어렵네요..ㅠㅜ

neocoin의 이미지

1. 테스트 해보라고 말씀밖에 못드립니다. 참고로 몇 만 건은 매우 작은 수준인거 같네요.

2. 다른쪽 이상입니다. 파일 포멧이 잘못된거 같군요. escape 이 잘못되어 있다건, 비어있는 column이 존재하거나, 수정하신 부분이 문제일듯 합니다.

작성하신 내용이 모호하지만.. 참고 삼아 에러를 재현하면..

>>> float("10.12345E+02")
1012.345
>>> float([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float() argument must be a string or a number

아마 파일들 중간 혹은 마지막에 빈줄이 하나 들어가 있을겁니다.

ps 주변에 python 이나 perl 을 하시는 분들과 잠시라도 짝프로그래밍을 수정하고 디버깅하면 쉽게 익힐수 있을 겁니다.
환경이 여의치 않다면, 초반 어려움은 감수하셔야 합니다.

그리고 python 에 의견은 http://bbs.python.or.kr/ 방법을 물어보시는 편이 좋을 것 같네요. 제가 요즘에는 python을 거의 사용하지를 않아서 큰 도움은 못될 것 같네요.

minic3000의 이미지

감사합니다.
반복적으로 테스트 하면서 코드 중간중간 확인해 보니, 입력자료가 중간에 짤리면서
그것도 짤린 마지막줄이 중간에서 짤리면서 컬럼이 비어있었습니다. 그래서 위 문제가 생긴 듯
하구요..
입력파일의 라인을 100줄 이하로 줄여서 테스트하니 정상적으로 동작합니다.

neocoin님 말씀처럼 초반 어려움은 감수하면서 진행해야 할 듯 합니다. 여러가지로 도움 주신 덕분에 그래도 한걸음 나아간 거 같아 기쁩니다.^^

amorette의 이미지

my %mat;
my $a = open "a.txt";
my $b = open "b.csv";
 
sub field ($id) { ($id / 10000).floor * 10000 }
 
for $a.lines, $b.lines -> $line {
    my ($id, @etc) = $line.split: /<[,;]> <.ws>/;
    my $f = field $id;
    %mat{$f} = %mat.exists($f) ?? %mat{$f} «+« @etc !! @etc;
}
 
for %mat.kv -> $k, @v { say ($k, @v).join: "," }

aero의 이미지

딱보니 csv모듈 같은 것도 쓸 필요 없을것 같아서 그냥 평범하게~
아래에 $p는 인덱스로 쓸 길이, $l은 기준되는 첫번째 필드의 길이
calc.pl

my ($p, $l) = (1, 5);
my %h;
while (<>) {
    my @a = split /[^\.\d]+/, $_;
    my $k = substr($a[0], -1*$l, $p);
    $h{substr($a[0], -1*$l, $p).'0'x($l- $p)}[$_-1] += $a[$_] for 1..$#a;
}
print "$_ @{$h{$_}}\n" for sort keys %h;

$ perl calc.pl a.txt b.csv
결과

10000 65 75
20000 101.1 81.1
30000 80.2 30.2

$p를 3으로 바꾸면
10000 33.45 33.45
10100 31.55 41.55
20000 101.1 81.1
30200 80.2 30.2

위를 우아함,엘레강스 함은 무시하고 원라이너로 줄이면

perl -aF"[^\.\d]+" -ne 'BEGIN{($p,$l)=(1,5)}$k=substr($F[0],-1*$l, $p);$h{substr($F[0],-$l,$p)."0"x($l-$p)}[$_-1]+=$F[$_] for 1..$#F;END{print"$_ @{$h{$_}}\n" for sort keys %h}; ' a.txt b.csv

neocoin의 이미지

이런 본문처럼 문법 하이라이트를 이 게시판에서 어떻게 작성하는건가요?

aero의 이미지

그리고 분리자 정규식 /[^\.\d]+/ 도 /[^.\d]+/ 로 수정되어야~
고치면
calc.pl

my ($p, $l) = (1, 5);
my %h;
while (<>) {
    my @a = split /[^.\d]+/, $_;
    $h{ substr($a[0], 0, $p).'0'x($l-$p) }[$_-1] += $a[$_] for 1..$#a;
}
print "$_ @{$h{$_}}\n" for sort keys %h;

원라이너 는

 perl -aF"[^.\d]+" -ne 'BEGIN{($p,$l)=(1,5)}$h{substr($F[0],0,$p)."0"x($l-$p)}[$_-1]+=$F[$_] for 1..$#F;END{print"$_ @{$h{$_}}\n" for sort keys %h}; ' a.txt b.csv

문법 하일라이팅은

(code lang=perl)
코드
(/code)

식으로 lang=언어를 지정해주시면 됩니다. 위에() 는 실제 쓸땐 <> 로 수정
aero의 이미지

.

minic3000의 이미지

답변 제시해주신 모든 분들께 감사드립니다.

덕분에 막혔던 길이 조금이나마 보이는 듯 합니다.^^

minic3000의 이미지

감사합니다. 한번 테스트 해봐야겠어요~^^

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.