Skip to content

Commit 9aafd6d

Browse files
committed
Merge pull request #10568 from roblevy/variable-round
ENH: Added DataFrame.round and associated tests
2 parents 2748e82 + dc57e2e commit 9aafd6d

File tree

5 files changed

+192
-0
lines changed

5 files changed

+192
-0
lines changed

doc/source/api.rst

+1
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,7 @@ Computations / Descriptive Stats
853853
DataFrame.prod
854854
DataFrame.quantile
855855
DataFrame.rank
856+
DataFrame.round
856857
DataFrame.sem
857858
DataFrame.skew
858859
DataFrame.sum

doc/source/options.rst

+2
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,5 @@ For instance:
438438
:suppress:
439439
440440
pd.reset_option('^display\.')
441+
442+
To round floats on a case-by-case basis, you can also use :meth:`~pandas.Series.round` and :meth:`~pandas.DataFrame.round`.

doc/source/whatsnew/v0.17.0.txt

+10
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ New features
6262
ser = pd.Series([np.nan, np.nan, 5, np.nan, np.nan, np.nan, 13])
6363
ser.interpolate(limit=1, limit_direction='both')
6464

65+
- Round DataFrame to variable number of decimal places (:issue:`10568`).
66+
67+
.. ipython :: python
68+
69+
df = pd.DataFrame(np.random.random([3, 3]), columns=['A', 'B', 'C'],
70+
index=['first', 'second', 'third'])
71+
df
72+
df.round(2)
73+
df.round({'A': 0, 'C': 2})
74+
6575
.. _whatsnew_0170.gil:
6676

6777
Releasing the GIL

pandas/core/frame.py

+70
Original file line numberDiff line numberDiff line change
@@ -4259,6 +4259,76 @@ def merge(self, right, how='inner', on=None, left_on=None, right_on=None,
42594259
left_index=left_index, right_index=right_index, sort=sort,
42604260
suffixes=suffixes, copy=copy)
42614261

4262+
def round(self, decimals=0, out=None):
4263+
"""
4264+
Round a DataFrame to a variable number of decimal places.
4265+
4266+
.. versionadded:: 0.17.0
4267+
4268+
Parameters
4269+
----------
4270+
decimals : int, dict, Series
4271+
Number of decimal places to round each column to. If an int is
4272+
given, round each column to the same number of places.
4273+
Otherwise dict and Series round to variable numbers of places.
4274+
Column names should be in the keys if `decimals` is a
4275+
dict-like, or in the index if `decimals` is a Series. Any
4276+
columns not included in `decimals` will be left as is. Elements
4277+
of `decimals` which are not columns of the input will be
4278+
ignored.
4279+
4280+
Examples
4281+
--------
4282+
>>> df = pd.DataFrame(np.random.random([3, 3]),
4283+
... columns=['A', 'B', 'C'], index=['first', 'second', 'third'])
4284+
>>> df
4285+
A B C
4286+
first 0.028208 0.992815 0.173891
4287+
second 0.038683 0.645646 0.577595
4288+
third 0.877076 0.149370 0.491027
4289+
>>> df.round(2)
4290+
A B C
4291+
first 0.03 0.99 0.17
4292+
second 0.04 0.65 0.58
4293+
third 0.88 0.15 0.49
4294+
>>> df.round({'A': 1, 'C': 2})
4295+
A B C
4296+
first 0.0 0.992815 0.17
4297+
second 0.0 0.645646 0.58
4298+
third 0.9 0.149370 0.49
4299+
>>> decimals = pd.Series([1, 0, 2], index=['A', 'B', 'C'])
4300+
>>> df.round(decimals)
4301+
A B C
4302+
first 0.0 1 0.17
4303+
second 0.0 1 0.58
4304+
third 0.9 0 0.49
4305+
4306+
Returns
4307+
-------
4308+
DataFrame object
4309+
"""
4310+
from pandas.tools.merge import concat
4311+
4312+
def _dict_round(df, decimals):
4313+
for col in df:
4314+
try:
4315+
yield np.round(df[col], decimals[col])
4316+
except KeyError:
4317+
yield df[col]
4318+
4319+
if isinstance(decimals, (dict, Series)):
4320+
new_cols = [col for col in _dict_round(self, decimals)]
4321+
elif com.is_integer(decimals):
4322+
# Dispatch to numpy.round
4323+
new_cols = [np.round(self[col], decimals) for col in self]
4324+
else:
4325+
raise TypeError("decimals must be an integer, a dict-like or a Series")
4326+
4327+
if len(new_cols) > 0:
4328+
return concat(new_cols, axis=1)
4329+
else:
4330+
return self
4331+
42624332
#----------------------------------------------------------------------
42634333
# Statistical methods, etc.
42644334

pandas/tests/test_format.py

+109
Original file line numberDiff line numberDiff line change
@@ -2680,6 +2680,115 @@ def test_to_csv_date_format(self):
26802680
self.assertEqual(df_day.to_csv(), expected_default_day)
26812681
self.assertEqual(df_day.to_csv(date_format='%Y-%m-%d'), expected_default_day)
26822682

2683+
def test_round_dataframe(self):
2684+
2685+
# GH 2665
2686+
2687+
# Test that rounding an empty DataFrame does nothing
2688+
df = DataFrame()
2689+
tm.assert_frame_equal(df, df.round())
2690+
2691+
# Here's the test frame we'll be working with
2692+
df = DataFrame(
2693+
{'col1': [1.123, 2.123, 3.123], 'col2': [1.234, 2.234, 3.234]})
2694+
2695+
# Default round to integer (i.e. decimals=0)
2696+
expected_rounded = DataFrame(
2697+
{'col1': [1., 2., 3.], 'col2': [1., 2., 3.]})
2698+
tm.assert_frame_equal(df.round(), expected_rounded)
2699+
2700+
# Round with an integer
2701+
decimals = 2
2702+
expected_rounded = DataFrame(
2703+
{'col1': [1.12, 2.12, 3.12], 'col2': [1.23, 2.23, 3.23]})
2704+
tm.assert_frame_equal(df.round(decimals), expected_rounded)
2705+
2706+
# This should also work with np.round (since np.round dispatches to
2707+
# df.round)
2708+
tm.assert_frame_equal(np.round(df, decimals), expected_rounded)
2709+
2710+
# Round with a list
2711+
round_list = [1, 2]
2712+
with self.assertRaises(TypeError):
2713+
df.round(round_list)
2714+
2715+
# Round with a dictionary
2716+
expected_rounded = DataFrame(
2717+
{'col1': [1.1, 2.1, 3.1], 'col2': [1.23, 2.23, 3.23]})
2718+
round_dict = {'col1': 1, 'col2': 2}
2719+
tm.assert_frame_equal(df.round(round_dict), expected_rounded)
2720+
2721+
# Incomplete dict
2722+
expected_partially_rounded = DataFrame(
2723+
{'col1': [1.123, 2.123, 3.123], 'col2': [1.2, 2.2, 3.2]})
2724+
partial_round_dict = {'col2': 1}
2725+
tm.assert_frame_equal(
2726+
df.round(partial_round_dict), expected_partially_rounded)
2727+
2728+
# Dict with unknown elements
2729+
wrong_round_dict = {'col3': 2, 'col2': 1}
2730+
tm.assert_frame_equal(
2731+
df.round(wrong_round_dict), expected_partially_rounded)
2732+
2733+
# float input to `decimals`
2734+
non_int_round_dict = {'col1': 1, 'col2': 0.5}
2735+
if sys.version < LooseVersion('2.7'):
2736+
# np.round([1.123, 2.123], 0.5) is only a warning in Python 2.6
2737+
with self.assert_produces_warning(DeprecationWarning):
2738+
df.round(non_int_round_dict)
2739+
else:
2740+
with self.assertRaises(TypeError):
2741+
df.round(non_int_round_dict)
2742+
2743+
# String input
2744+
non_int_round_dict = {'col1': 1, 'col2': 'foo'}
2745+
with self.assertRaises(TypeError):
2746+
df.round(non_int_round_dict)
2747+
2748+
non_int_round_Series = Series(non_int_round_dict)
2749+
with self.assertRaises(TypeError):
2750+
df.round(non_int_round_Series)
2751+
2752+
# List input
2753+
non_int_round_dict = {'col1': 1, 'col2': [1, 2]}
2754+
with self.assertRaises(TypeError):
2755+
df.round(non_int_round_dict)
2756+
2757+
non_int_round_Series = Series(non_int_round_dict)
2758+
with self.assertRaises(TypeError):
2759+
df.round(non_int_round_Series)
2760+
2761+
# Non integer Series inputs
2762+
non_int_round_Series = Series(non_int_round_dict)
2763+
with self.assertRaises(TypeError):
2764+
df.round(non_int_round_Series)
2765+
2766+
non_int_round_Series = Series(non_int_round_dict)
2767+
with self.assertRaises(TypeError):
2768+
df.round(non_int_round_Series)
2769+
2770+
# Negative numbers
2771+
negative_round_dict = {'col1': -1, 'col2': -2}
2772+
big_df = df * 100
2773+
expected_neg_rounded = DataFrame(
2774+
{'col1':[110., 210, 310], 'col2':[100., 200, 300]})
2775+
tm.assert_frame_equal(
2776+
big_df.round(negative_round_dict), expected_neg_rounded)
2777+
2778+
# nan in Series round
2779+
nan_round_Series = Series({'col1': nan, 'col2':1})
2780+
expected_nan_round = DataFrame(
2781+
{'col1': [1.123, 2.123, 3.123], 'col2': [1.2, 2.2, 3.2]})
2782+
if sys.version < LooseVersion('2.7'):
2783+
# Rounding with decimal is a ValueError in Python < 2.7
2784+
with self.assertRaises(ValueError):
2785+
df.round(nan_round_Series)
2786+
else:
2787+
with self.assertRaises(TypeError):
2788+
df.round(nan_round_Series)
2789+
2790+
# Make sure this doesn't break existing Series.round
2791+
tm.assert_series_equal(df['col1'].round(1), expected_rounded['col1'])
26832792

26842793
class TestSeriesFormatting(tm.TestCase):
26852794
_multiprocess_can_split_ = True

0 commit comments

Comments
 (0)