pandas: Round, floor, and ceil for DataFrame and Series

Posted: | Tags: Python, pandas

You can round numbers in pandas.DataFrame and pandas.Series using the round() method. Note that it uses bankers' rounding, which means it rounds half to even (e.g., 0.5 rounds to 0.0).

For standard rounding (rounding half up) and for rounding down and up decimals (floor and ceiling), use functions from NumPy or the decimal module in Python's standard library.

For more information about Python's built-in round() function and NumPy's np.round(), see the following articles.

The pandas version used in this article is as follows. Note that functionality may vary between versions.

import pandas as pd

print(pd.__version__)
# 2.1.2

The round() method of pandas.Series

Consider a Series with floating point numbers (float).

s_f = pd.Series([123.456, 987.654])
print(s_f)
# 0    123.456
# 1    987.654
# dtype: float64

Specify the number of digits for rounding with the first argument, decimals. The default is decimals=0, rounding to zero decimal places, but the data type remains float. To convert to an integer (int), use the astype() method.

print(s_f.round())
# 0    123.0
# 1    988.0
# dtype: float64

print(s_f.round().astype(int))
# 0    123
# 1    988
# dtype: int64

Positive decimals values round to the specified number of decimal places, while negative values round to the corresponding integer places (e.g., -1 for tens, -2 for hundreds).

print(s_f.round(2))
# 0    123.46
# 1    987.65
# dtype: float64

print(s_f.round(-2))
# 0     100.0
# 1    1000.0
# dtype: float64

For a Series with integers (int), if decimals is 0 (or if omitted) or positive, there is no change. If negative, it rounds to the corresponding integer place.

s_i = pd.Series([123, 987])
print(s_i)
# 0    123
# 1    987
# dtype: int64

print(s_i.round())
# 0    123
# 1    987
# dtype: int64

print(s_i.round(2))
# 0    123
# 1    987
# dtype: int64

print(s_i.round(-2))
# 0     100
# 1    1000
# dtype: int64

The round() method returns a new object with rounded numbers, without changing the original object. The same applies to the round() method of DataFrame.

s_i_round = s_i.round(-2)
print(s_i_round)
# 0     100
# 1    1000
# dtype: int64

print(s_i)
# 0    123
# 1    987
# dtype: int64

Note that the round() method rounds half to even (e.g., 0.5 rounds to 0.0). Details will be discussed later.

The round() method of pandas.DataFrame

Consider a DataFrame with columns of floating point numbers (float), integers (int), and strings (str).

df = pd.DataFrame({'f': [123.456, 987.654], 'i': [123, 987], 's': ['abc', 'xyz']})
print(df)
#          f    i    s
# 0  123.456  123  abc
# 1  987.654  987  xyz

print(df.dtypes)
# f    float64
# i      int64
# s     object
# dtype: object

Specify digits for all columns

If you omit the argument of round() or specify an integer, all columns are rounded to the corresponding number of digits. String columns remain unchanged.

print(df.round())
#        f    i    s
# 0  123.0  123  abc
# 1  988.0  987  xyz

print(df.round(2))
#         f    i    s
# 0  123.46  123  abc
# 1  987.65  987  xyz

print(df.round(-2))
#         f     i    s
# 0   100.0   100  abc
# 1  1000.0  1000  xyz

Specify digits individually for each column

You can specify the number of digits for each column individually using a dictionary, with column names as keys and the number of digits as values. This specification does not affect columns with string data. Any columns not included in the dictionary will remain unchanged.

print(df.round({'f': 2, 'i': -1, 's': 2}))
#         f    i    s
# 0  123.46  120  abc
# 1  987.65  990  xyz

print(df.round({'i': -2}))
#          f     i    s
# 0  123.456   100  abc
# 1  987.654  1000  xyz

The round() method rounds half to even (bankers' rounding)

The round() method in pandas uses bankers' rounding, which means it rounds half to even. For example, 0.5 and 2.5 are rounded to 0.0 and 2.0, respectively.

s = pd.Series([0.5, 1.5, 2.5, 3.5, 4.5])
print(s)
# 0    0.5
# 1    1.5
# 2    2.5
# 3    3.5
# 4    4.5
# dtype: float64

print(s.round())
# 0    0.0
# 1    2.0
# 2    2.0
# 3    4.0
# 4    4.0
# dtype: float64

Rounding to even occurs only when the fraction is exactly 0.5; for example, 2.5 is rounded to 2.0, but 2.51 is rounded to 3.0.

s = pd.Series([2.49, 2.5, 2.51])
print(s)
# 0    2.49
# 1    2.50
# 2    2.51
# dtype: float64

print(s.round())
# 0    2.0
# 1    2.0
# 2    3.0
# dtype: float64

This applies similarly when specifying the number of digits.

s = pd.Series([249, 250, 251])
print(s)
# 0    249
# 1    250
# 2    251
# dtype: int64

print(s.round(-2))
# 0    200
# 1    200
# 2    300
# dtype: int64

Round, floor, and ceil with NumPy functions

As of version 2.1, pandas does not provide methods for standard rounding (rounding half up) and rounding down and up decimals (floor and ceiling). Instead, you can use NumPy or the decimal module in Python's standard library for these operations.

You can use NumPy functions for Series and DataFrame.

import numpy as np

s = pd.Series([-0.5, -0.25, 0.25, 0.5])
print(s)
# 0   -0.50
# 1   -0.25
# 2    0.25
# 3    0.50
# dtype: float64

print(np.floor(s))
# 0   -1.0
# 1   -1.0
# 2    0.0
# 3    0.0
# dtype: float64

print(np.trunc(s))
# 0   -0.0
# 1   -0.0
# 2    0.0
# 3    0.0
# dtype: float64

print(np.ceil(s))
# 0   -0.0
# 1   -0.0
# 2    1.0
# 3    1.0
# dtype: float64

np.floor() rounds toward negative infinity, np.trunc() rounds toward zero, np.ceil() rounds toward positive infinity. For details, refer to the following article.

Although NumPy does not have a function for standard rounding (rounding half up), you can define a custom function to achieve this as follows.

def my_round(x, decimals=0):
    return np.floor(x * 10**decimals + 0.5) / 10**decimals

s = pd.Series([0.5, 1.5, 2.5, 3.5, 4.5])
print(s)
# 0    0.5
# 1    1.5
# 2    2.5
# 3    3.5
# 4    4.5
# dtype: float64

print(my_round(s))
# 0    1.0
# 1    2.0
# 2    3.0
# 3    4.0
# 4    5.0
# dtype: float64

print(my_round(s * 10, -1))
# 0    10.0
# 1    20.0
# 2    30.0
# 3    40.0
# 4    50.0
# dtype: float64

Note that applying NumPy functions to a DataFrame containing non-numeric columns like strings can result in errors. You can use select_dtypes() to extract only numeric columns.

df = pd.DataFrame({'f': [123.456, 987.654], 'i': [123, 987], 's': ['abc', 'xyz']})
print(df)
#          f    i    s
# 0  123.456  123  abc
# 1  987.654  987  xyz

# print(np.floor(df))
# TypeError: must be real number, not str

print(np.floor(df.select_dtypes('number')))
#        f      i
# 0  123.0  123.0
# 1  987.0  987.0

It's also possible to process the desired columns and add them as new columns.

df['f_floor'] = np.floor(df['f'])
print(df)
#          f    i    s  f_floor
# 0  123.456  123  abc    123.0
# 1  987.654  987  xyz    987.0

Round, floor, and ceil with Python's decimal module

Using Python's standard library decimal module, you can handle precise decimal floating point numbers.

Though slower than pandas or NumPy methods/functions, the decimal module offers precise decimal processing in Series and DataFrame.

Import as follows:

from decimal import Decimal, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING

Convert numbers to strings using astype() and then apply Decimal() to each element using map().

s = pd.Series([0.123, 0.456, 0.789])
print(s)
# 0    0.123
# 1    0.456
# 2    0.789
# dtype: float64

s_decimal = s.astype(str).map(Decimal)
print(s_decimal)
# 0    0.123
# 1    0.456
# 2    0.789
# dtype: object

print(type(s_decimal[0]))
# <class 'decimal.Decimal'>

Note that if not converted to a string, it will be treated as a value that can be represented by a floating point number (float).

print(s.map(Decimal))
# 0    0.12299999999999999822364316059974953532218933...
# 1    0.45600000000000001643130076445231679826974868...
# 2    0.78900000000000003463895836830488406121730804...
# dtype: object

The quantize() method of Decimal objects allows you to round to a specified number of digits with various rounding modes.

print(s_decimal.map(lambda x: x.quantize(Decimal('0.1'), ROUND_HALF_UP)))
# 0    0.1
# 1    0.5
# 2    0.8
# dtype: object

print(s_decimal.map(lambda x: x.quantize(Decimal('0.1'), ROUND_FLOOR)))
# 0    0.1
# 1    0.4
# 2    0.7
# dtype: object

print(s_decimal.map(lambda x: x.quantize(Decimal('0.1'), ROUND_CEILING)))
# 0    0.2
# 1    0.5
# 2    0.8
# dtype: object

For the first argument, specify a Decimal with the desired number of digits, using a string format such as '0.1' or '0.01'. For rounding the integer part, use scientific notation, like '1E1'. For more details, refer to the following article.

Specify the rounding mode as the second argument, such as ROUND_HALF_UP for rounding half up, ROUND_FLOOR for rounding toward negative infinity, and ROUND_CEILING for rounding toward positive infinity. Other rounding modes can be found in the official documentation.

It's also possible to convert a Series or DataFrame with Decimal objects back to floating point numbers (float) using astype(). However, the values will be converted to those representable by float, so they may include errors.

print(s_decimal.astype(float))
# 0    0.123
# 1    0.456
# 2    0.789
# dtype: float64

print(s_decimal.astype(float).map(Decimal))
# 0    0.12299999999999999822364316059974953532218933...
# 1    0.45600000000000001643130076445231679826974868...
# 2    0.78900000000000003463895836830488406121730804...
# dtype: object

Related Categories

Related Articles