A piece of Python code to compute a frequency table of a list of values I have written a Python 2.5.4 function which accepts as input a list of numbers alongwith the desired number of classes and yields a frequency distribution (i.e. a histogram) of the same data. 

While setting class boundaries, I've attempted to increase the accuracy (i.e. number of digits after the decimal point). 

Here's my code: 

def num_after_point(x):
    s = str(x)
    if '.' not in s:
        return 0
    return len(s) - s.index('.') - 1

def histogram(list, numBuckets):
    count = [0] * len(list)
    for i in range(len(list)):
        count[i] = num_after_point(list[i])
    lowest = min(list) - 10**(-max(count)-1)
    highest = max(list) + 10**(-max(count) -1)
    print "Lowest data value:", lowest
    print "Highest data value:", highest
    dataRange = highest - lowest
    bucketSize = dataRange / numBuckets
    print "Bucket Size:", bucketSize
    bucketCountList = [0] * numBuckets
    low = lowest
    high = lowest + bucketSize
    index = 0
    for i in range(len(list)):
        if low &lt; list[i] &lt; high:
            bucketCountList[index] += 1
    print "From", low, "to", high, ":", bucketCountList[index]
    while high &lt; highest:
        low = high
        high = low + bucketSize
        index = index + 1
        for i in range(len(list)):
            if low &lt; list[i] &lt; high:
                bucketCountList[index] += 1
        print "From", low, "to", high, ":", bucketCountList[index]

histogram( [ 0.1, 0.22, 0.333, 0.4444, 0.55555, 0.666666,   
             0.7777777, 0.88888888, 0.999999999, 1.0], 3)


Is this code error-free enough?

If not, where and how does it break down?

How can I improve its functionality and efficiency?
You may want to try 2.5.6, a security fix to 2.5.4.
Alternately either 2.7 or, preferably, 3 would be a better upgrade.
They may be unfeasible, but you should upgrade, you're 7 years behind!



First things first, don't do hacky stuff that give incorrect bounds.


  lowest = min(list) - 10**(-max(count)-1)


The correct bounds are not the ones you are using!
They are:

[0.1, 0.4)
[0.4, 0.7)
[0.7, 1.0]


All bar the last 1 inclusive is easy, just remove that hack you are using.
If we put the bound creator in a function then we can create the bounds and re-use them in a future function.
To handle the 1 inclusive you can change it to a 2, (1 + 1).

I would also use comprehensions, they are quite simple, but extremely useful.
For example creating count would be:

count = [num_after_point(list[i]) for i in range(len(list))]


This can be improved by not indexing list and not using len and range.

count = [num_after_point(i) for i in list]


As most of your function is creating the bounds I'll go through it first.
You want to keep most of your code the same. But:


Remove num_after_point, count and all that mess.
Remove all the bucketCountList stuff.
Change the while loop to a for loop for _ in range(numBuckets - 1):.
Yes you want the -1.
After changing low and high, yield them. (yield low, high).
After the for loop yield high, highest + 1, this is the only edge case we will be using.


This should get you:

def bounds(list_, numBuckets):
    lowest = min(list_)
    highest = max(list_)
    bucket_size = (highest - lowest) / numBuckets
    upper_bound = lowest
    for _ in range(numBuckets - 1):
        lower_bound, upper_bound = upper_bound, upper_bound + bucket_size
        yield lower_bound, upper_bound
    yield upper_bound, highest + 1


Now to use it is very easy:


You want to go through all the lower and upper bounds of the bounds function.
simply for l, u in bounds(...):.
Generate the sum in the bounds.
Display everything.


To get:

def histogram(list_, num_buckets):
    for lower_bound, upper_bound in bounds(list_, num_buckets):
        data = sum(lower_bound &lt;= i &lt; upper_bound for i in list_)
        print "From", lower_bound, "to", upper_bound, ":", data


The only down-side is the one hack we used to include the upper bound.
This has a small impact on the display, but ultimately it's a much nicer solution.


The bounds are easier to read.
You can use every number in the range. You couldn't use 0.0999999999 for example.


Finally, not knowing the numbers in the range is a bit... odd.
You should probably allow that, which is using a comprehensions if.

def histogram(list_, num_buckets):
    for lower_bound, upper_bound in bounds(list_, num_buckets):
        data = [i for i in list_ if lower_bound &lt;= i &lt; upper_bound]
        print "From", lower_bound, "to", upper_bound, ":", len(data)
        print data