~toolpart/openobject-server/toolpart

« back to all changes in this revision

Viewing changes to bin/pychart/chart_data.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright (C) 2000-2005 by Yasushi Saito (yasushi.saito@gmail.com)
 
3
 
4
# Jockey is free software; you can redistribute it and/or modify it
 
5
# under the terms of the GNU General Public License as published by the
 
6
# Free Software Foundation; either version 2, or (at your option) any
 
7
# later version.
 
8
#
 
9
# Jockey is distributed in the hope that it will be useful, but WITHOUT
 
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 
11
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 
12
# for more details.
 
13
#
 
14
import pychart_util
 
15
import copy
 
16
import math
 
17
 
 
18
def _convert_item(v, typ, line):
 
19
    if typ == "a":
 
20
        try:
 
21
            i = float(v)
 
22
        except ValueError: # non-number
 
23
            i = v
 
24
        return i
 
25
    elif typ == "d":
 
26
        try:
 
27
            return int(v)
 
28
        except ValueError:
 
29
            raise ValueError, "Can't convert %s to int; line=%s" % (v, line)
 
30
    elif typ == "f":
 
31
        try:
 
32
            return float(v)
 
33
        except ValueError:
 
34
            raise ValueError, "Can't convert %s to float; line=%s" % (v, line)
 
35
    elif typ == "s":
 
36
        return v
 
37
    else:
 
38
        raise ValueError, "Unknown conversion type, type=%s; line=%s" % (typ,line)
 
39
        
 
40
def parse_line(line, delim):
 
41
    if delim.find("%") < 0:
 
42
        return [ _convert_item(item, "a", None) for item in line.split(delim) ]
 
43
    
 
44
    data = []
 
45
    idx = 0 # indexes delim
 
46
    ch = 'f'
 
47
    sep = ','
 
48
 
 
49
    while idx < len(delim):
 
50
        if delim[idx] != '%':
 
51
            raise ValueError, "bad delimitor: '" + delim + "'"
 
52
        ch = delim[idx+1]
 
53
        idx += 2
 
54
        sep = ""
 
55
        while idx < len(delim) and delim[idx] != '%':
 
56
            sep += delim[idx]
 
57
            idx += 1
 
58
        xx = line.split(sep, 1)
 
59
        data.append(_convert_item(xx[0], ch, line))
 
60
        if len(xx) >= 2:
 
61
            line = xx[1]
 
62
        else:
 
63
            line = ""
 
64
            break
 
65
 
 
66
    if line != "":
 
67
        for item in line.split(sep):
 
68
            data.append(_convert_item(item, ch, line))
 
69
    return data
 
70
 
 
71
def escape_string(str):
 
72
    return str.replace("/", "//")
 
73
 
 
74
def extract_rows(data, *rows):
 
75
    """Extract rows specified in the argument list.
 
76
 
 
77
>>> chart_data.extract_rows([[10,20], [30,40], [50,60]], 1, 2)
 
78
[[30,40],[50,60]]
 
79
"""
 
80
    try:
 
81
        # for python 2.2
 
82
        # return [data[r] for r in rows]
 
83
        out = []
 
84
        for r in rows:
 
85
            out.append(data[r])
 
86
        return out
 
87
    except IndexError:
 
88
        raise IndexError, "data=%s rows=%s" % (data, rows)
 
89
    return out
 
90
 
 
91
def extract_columns(data, *cols):
 
92
    """Extract columns specified in the argument list.
 
93
 
 
94
>>> chart_data.extract_columns([[10,20], [30,40], [50,60]], 0)
 
95
[[10],[30],[50]]
 
96
"""
 
97
    out = []
 
98
    try:
 
99
        # for python 2.2:
 
100
        # return [ [r[c] for c in cols] for r in data]
 
101
        for r in data:
 
102
            col = []
 
103
            for c in cols:
 
104
                col.append(r[c])
 
105
            out.append(col)
 
106
    except IndexError:
 
107
        raise IndexError, "data=%s col=%s" % (data, col)        
 
108
    return out
 
109
 
 
110
            
 
111
            
 
112
 
 
113
def moving_average(data, xcol, ycol, width):
 
114
    """Compute the moving average of  YCOL'th column of each sample point
 
115
in  DATA. In particular, for each element  I in  DATA,
 
116
this function extracts up to  WIDTH*2+1 elements, consisting of
 
117
 I itself,  WIDTH elements before  I, and  WIDTH
 
118
elements after  I. It then computes the mean of the  YCOL'th
 
119
column of these elements, and it composes a two-element sample
 
120
consisting of  XCOL'th element and the mean.
 
121
 
 
122
>>> data = [[10,20], [20,30], [30,50], [40,70], [50,5]]
 
123
... chart_data.moving_average(data, 0, 1, 1)
 
124
[(10, 25.0), (20, 33.333333333333336), (30, 50.0), (40, 41.666666666666664), (50, 37.5)]
 
125
 
 
126
  The above value actually represents:
 
127
 
 
128
[(10, (20+30)/2), (20, (20+30+50)/3), (30, (30+50+70)/3), 
 
129
  (40, (50+70+5)/3), (50, (70+5)/2)]
 
130
 
 
131
"""
 
132
 
 
133
    
 
134
    out = []
 
135
    try:
 
136
        for i in range(len(data)):
 
137
            n = 0
 
138
            total = 0
 
139
            for j in range(i-width, i+width+1):
 
140
                if j >= 0 and j < len(data):
 
141
                    total += data[j][ycol]
 
142
                    n += 1
 
143
            out.append((data[i][xcol], float(total) / n))
 
144
    except IndexError:
 
145
        raise IndexError, "bad data: %s,xcol=%d,ycol=%d,width=%d" % (data,xcol,ycol,width)
 
146
    
 
147
    return out
 
148
    
 
149
def filter(func, data):
 
150
    """Parameter <func> must be a single-argument
 
151
    function that takes a sequence (i.e.,
 
152
a sample point) and returns a boolean. This procedure calls <func> on
 
153
each element in <data> and returns a list comprising elements for
 
154
which <func> returns True.
 
155
 
 
156
>>> data = [[1,5], [2,10], [3,13], [4,16]]
 
157
... chart_data.filter(lambda x: x[1] % 2 == 0, data)
 
158
[[2,10], [4,16]].
 
159
"""
 
160
    
 
161
    out = []
 
162
    for r in data:
 
163
        if func(r):
 
164
            out.append(r)
 
165
    return out
 
166
 
 
167
def transform(func, data):
 
168
    """Apply <func> on each element in <data> and return the list
 
169
consisting of the return values from <func>.
 
170
 
 
171
>>> data = [[10,20], [30,40], [50,60]]
 
172
... chart_data.transform(lambda x: [x[0], x[1]+1], data)
 
173
[[10, 21], [30, 41], [50, 61]]
 
174
 
 
175
"""
 
176
    out = []
 
177
    for r in data:
 
178
        out.append(func(r))
 
179
    return out
 
180
 
 
181
def aggregate_rows(data, col):
 
182
    out = copy.deepcopy(data)
 
183
    total = 0
 
184
    for r in out:
 
185
        total += r[col]
 
186
        r[col] = total
 
187
    return out
 
188
 
 
189
def empty_line_p(s):
 
190
    return s.strip() == ""
 
191
 
 
192
def fread_csv(fd, delim = ','):
 
193
    """This function is similar to read_csv, except that it reads from
 
194
    an open file handle <fd>, or any object that provides method "readline".
 
195
 
 
196
fd = open("foo", "r")
 
197
data = chart_data.fread_csv(fd, ",") """
 
198
    
 
199
    data = []
 
200
    line = fd.readline()
 
201
    while line != "":
 
202
        if line[0] != '#' and not empty_line_p(line):
 
203
            data.append(parse_line(line, delim))
 
204
        line = fd.readline()
 
205
    return data
 
206
 
 
207
def read_csv(path, delim = ','):
 
208
    """This function reads
 
209
    comma-separated values from file <path>. Empty lines and lines
 
210
    beginning with "#" are ignored.  Parameter <delim> specifies how
 
211
    a line is separated into values. If it does not contain the
 
212
    letter "%", then <delim> marks the end of a value.
 
213
    Otherwise, this function acts like scanf in C:
 
214
 
 
215
chart_data.read_csv("file", "%d,%s:%d")
 
216
 
 
217
    Paramter <delim> currently supports
 
218
    only three conversion format specifiers:
 
219
    "d"(int), "f"(double), and "s"(string)."""
 
220
        
 
221
    f = open(path)
 
222
    data = fread_csv(f, delim)
 
223
    f.close()
 
224
    return data
 
225
 
 
226
def fwrite_csv(fd, data):
 
227
    """This function writes comma-separated <data> to <fd>. Parameter <fd> must be a file-like object
 
228
    that supports the |write()| method."""
 
229
    for v in data:
 
230
        fd.write(",".join([str(x) for x in v]))
 
231
        fd.write("\n")
 
232
        
 
233
def write_csv(path, data):
 
234
    """This function writes comma-separated values to <path>."""
 
235
    fd = file(path, "w")
 
236
    fwrite_csv(fd, data)
 
237
    fd.close()
 
238
    
 
239
def read_str(delim = ',', *lines):
 
240
    """This function is similar to read_csv, but it reads data from the
 
241
    list of <lines>.
 
242
 
 
243
fd = open("foo", "r")
 
244
data = chart_data.read_str(",", fd.readlines())"""
 
245
 
 
246
    data = []
 
247
    for line in lines:
 
248
        com = parse_line(line, delim)
 
249
        data.append(com)
 
250
    return data
 
251
    
 
252
def func(f, xmin, xmax, step = None):
 
253
    """Create sample points from function <f>, which must be a
 
254
    single-parameter function that returns a number (e.g., math.sin).
 
255
    Parameters <xmin> and <xmax> specify the first and last X values, and
 
256
    <step> specifies the sampling interval.
 
257
 
 
258
>>> chart_data.func(math.sin, 0, math.pi * 4, math.pi / 2)
 
259
[(0, 0.0), (1.5707963267948966, 1.0), (3.1415926535897931, 1.2246063538223773e-16), (4.7123889803846897, -1.0), (6.2831853071795862, -2.4492127076447545e-16), (7.8539816339744828, 1.0), (9.4247779607693793, 3.6738190614671318e-16), (10.995574287564276, -1.0)]
 
260
 
 
261
"""
 
262
    
 
263
    data = []
 
264
    x = xmin
 
265
    if not step:
 
266
        step = (xmax - xmin) / 100.0
 
267
    while x < xmax:
 
268
        data.append((x, f(x)))
 
269
        x += step
 
270
    return data
 
271
 
 
272
def _nr_data(data, col):
 
273
    nr_data = 0
 
274
    for d in data:
 
275
        nr_data += d[col]
 
276
    return nr_data
 
277
    
 
278
def median(data, freq_col=1):
 
279
    """Compute the median of the <freq_col>'th column of the values is <data>.
 
280
 
 
281
>>> chart_data.median([(10,20), (20,4), (30,5)], 0)
 
282
20
 
283
>>> chart_data.median([(10,20), (20,4), (30,5)], 1)
 
284
5.
 
285
    """
 
286
    
 
287
    nr_data = _nr_data(data, freq_col)
 
288
    median_idx = nr_data / 2
 
289
    i = 0
 
290
    for d in data:
 
291
        i += d[freq_col]
 
292
        if i >= median_idx:
 
293
            return d
 
294
    raise Exception, "??? median ???"
 
295
 
 
296
def cut_extremes(data, cutoff_percentage, freq_col=1):
 
297
    nr_data = _nr_data(data, freq_col)
 
298
    min_idx = nr_data * cutoff_percentage / 100.0
 
299
    max_idx = nr_data * (100 - cutoff_percentage) / 100.0
 
300
    r = []
 
301
    
 
302
    i = 0
 
303
    for d in data:
 
304
        if i < min_idx:
 
305
            if i + d[freq_col] >= min_idx:
 
306
                x = copy.deepcopy(d)
 
307
                x[freq_col] = x[freq_col] - (min_idx - i)
 
308
                r.append(x)
 
309
            i += d[freq_col]
 
310
            continue
 
311
        elif i + d[freq_col] >= max_idx:
 
312
            if i < max_idx and i + d[freq_col] >= max_idx:
 
313
                x = copy.deepcopy(d)
 
314
                x[freq_col] = x[freq_col] - (max_idx - i)
 
315
                r.append(x)
 
316
            break
 
317
        i += d[freq_col]
 
318
        r.append(d)
 
319
    return r
 
320
 
 
321
def mean(data, val_col, freq_col):
 
322
    nr_data = 0
 
323
    sum = 0
 
324
    for d in data:
 
325
        sum += d[val_col] * d[freq_col]
 
326
        nr_data += d[freq_col]
 
327
    if nr_data == 0:
 
328
        raise IndexError, "data is empty"
 
329
 
 
330
    return sum / float(nr_data)
 
331
 
 
332
def mean_samples(data, xcol, ycollist):
 
333
    """Create a sample list that contains
 
334
    the mean of the original list.
 
335
 
 
336
>>> chart_data.mean_samples([ [1, 10, 15], [2, 5, 10], [3, 8, 33] ], 0, (1, 2))
 
337
[(1, 12.5), (2, 7.5), (3, 20.5)]
 
338
"""
 
339
    out = []
 
340
    numcol = len(ycollist)
 
341
    try:
 
342
        for elem in data:
 
343
            v = 0
 
344
            for col in ycollist:
 
345
                v += elem[col]
 
346
            out.append( (elem[xcol], float(v) / numcol) )
 
347
    except IndexError:
 
348
        raise IndexError, "bad data: %s,xcol=%d,ycollist=%s" % (data,xcol,ycollist)
 
349
    
 
350
    return out
 
351
 
 
352
def stddev_samples(data, xcol, ycollist, delta = 1.0):
 
353
    """Create a sample list that contains the mean and standard deviation of the original list. Each element in the returned list contains following values: [MEAN, STDDEV, MEAN - STDDEV*delta, MEAN + STDDEV*delta].
 
354
 
 
355
>>> chart_data.stddev_samples([ [1, 10, 15, 12, 15], [2, 5, 10, 5, 10], [3, 32, 33, 35, 36], [4,16,66, 67, 68] ], 0, range(1,5))
 
356
[(1, 13.0, 2.1213203435596424, 10.878679656440358, 15.121320343559642), (2, 7.5, 2.5, 5.0, 10.0), (3, 34.0, 1.5811388300841898, 32.418861169915807, 35.581138830084193), (4, 54.25, 22.094965489902897, 32.155034510097103, 76.344965489902904)]
 
357
"""
 
358
    out = []
 
359
    numcol = len(ycollist)
 
360
    try:
 
361
        for elem in data:
 
362
            total = 0
 
363
            for col in ycollist:
 
364
                total += elem[col]
 
365
            mean = float(total) / numcol
 
366
            variance = 0
 
367
            for col in ycollist:
 
368
                variance += (mean - elem[col]) ** 2
 
369
            stddev = math.sqrt(variance / numcol) * delta
 
370
            out.append( (elem[xcol], mean, stddev, mean-stddev, mean+stddev) )
 
371
            
 
372
            
 
373
            
 
374
    except IndexError:
 
375
        raise IndexError, "bad data: %s,xcol=%d,ycollist=%s" % (data,xcol,ycollist)
 
376
    return out
 
377
 
 
378
def nearest_match(data, col, val):
 
379
    min_delta = None
 
380
    match = None
 
381
    
 
382
    for d in data:
 
383
        if min_delta == None or abs(d[col] - val) < min_delta:
 
384
            min_delta = abs(d[col] - val)
 
385
            match = d
 
386
    pychart_util.warn("XXX ", match)
 
387
    return match