summaryrefslogtreecommitdiffstats
path: root/update_payload/histogram.py
blob: 1ac2ab5d48f4203296e9dfc658a6dc739c5b751e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""Histogram generation tools."""

from collections import defaultdict

from update_payload import format_utils


class Histogram(object):
  """A histogram generating object.

  This object serves the sole purpose of formatting (key, val) pairs as an
  ASCII histogram, including bars and percentage markers, and taking care of
  label alignment, scaling, etc. In addition to the standard __init__
  interface, two static methods are provided for conveniently converting data
  in different formats into a histogram. Histogram generation is exported via
  its __str__ method, and looks as follows:

    Yes |################    | 5 (83.3%)
    No  |###                 | 1 (16.6%)

  TODO(garnold) we may want to add actual methods for adding data or tweaking
  the output layout and formatting. For now, though, this is fine.

  """

  def __init__(self, data, scale=20, formatter=None):
    """Initialize a histogram object.

    Args:
      data: list of (key, count) pairs constituting the histogram
      scale: number of characters used to indicate 100%
      formatter: function used for formatting raw histogram values

    """
    self.data = data
    self.scale = scale
    self.formatter = formatter or str
    self.max_key_len = max([len(str(key)) for key, count in self.data])
    self.total = sum([count for key, count in self.data])

  @staticmethod
  def FromCountDict(count_dict, scale=20, formatter=None, key_names=None):
    """Takes a dictionary of counts and returns a histogram object.

    This simply converts a mapping from names to counts into a list of (key,
    count) pairs, optionally translating keys into name strings, then
    generating and returning a histogram for them. This is a useful convenience
    call for clients that update a dictionary of counters as they (say) scan a
    data stream.

    Args:
      count_dict: dictionary mapping keys to occurrence counts
      scale: number of characters used to indicate 100%
      formatter: function used for formatting raw histogram values
      key_names: dictionary mapping keys to name strings
    Returns:
      A histogram object based on the given data.

    """
    namer = None
    if key_names:
      namer = lambda key: key_names[key]
    else:
      namer = lambda key: key

    hist = [(namer(key), count) for key, count in count_dict.items()]
    return Histogram(hist, scale, formatter)

  @staticmethod
  def FromKeyList(key_list, scale=20, formatter=None, key_names=None):
    """Takes a list of (possibly recurring) keys and returns a histogram object.

    This converts the list into a dictionary of counters, then uses
    FromCountDict() to generate the actual histogram. For example:

      ['a', 'a', 'b', 'a', 'b'] --> {'a': 3, 'b': 2} --> ...

    Args:
      key_list: list of (possibly recurring) keys
      scale: number of characters used to indicate 100%
      formatter: function used for formatting raw histogram values
      key_names: dictionary mapping keys to name strings
    Returns:
      A histogram object based on the given data.

    """
    count_dict = defaultdict(int)  # Unset items default to zero
    for key in key_list:
      count_dict[key] += 1
    return Histogram.FromCountDict(count_dict, scale, formatter, key_names)

  def __str__(self):
    hist_lines = []
    hist_bar = '|'
    for key, count in self.data:
      if self.total:
        bar_len = count * self.scale / self.total
        hist_bar = '|%s|' % ('#' * bar_len).ljust(self.scale)

      line = '%s %s %s' % (
          str(key).ljust(self.max_key_len),
          hist_bar,
          self.formatter(count))
      percent_str = format_utils.NumToPercent(count, self.total)
      if percent_str:
        line += ' (%s)' % percent_str
      hist_lines.append(line)

    return '\n'.join(hist_lines)

  def GetKeys(self):
    """Returns the keys of the histogram."""
    return [key for key, _ in self.data]