Round series to specific values, that are not multiples?

Viewed 51

I am trying to round the values of a series based on a list of values that are not multiples. The values I'm trying to round to are 15, 30, 60 and 120.

You can see that I cannot use multiples of 15 because that would encompass 45, 75, 90..., which I do not want to round to.

Context

I'm working on a project that evaluates the length of radio ads. In the radio industry ads are typically 15, 30, 60 or 120 seconds in length. The reason the data may be off is because the tracking software marks the beginning and end of the ad itself not how long it's suppose to be. So the recorded length gets thrown off when the ad gets cut off or the tracking software tracks part of the next ad by accident.

My Code

import pandas as pd

# Example of series I want to round
values_to_be_rounded = pd.Series([30, 101, 20, 48, 60])

# Values I want to round to
round_to_list = [15, 30, 60, 120]

# Create list of lists for the absolute difference between each index in round_to_list and values_to_be_rounded.
difference = [abs(values_to_be_rounded-round_to_list[0]), abs(values_to_be_rounded-round_to_list[1]),
              abs(values_to_be_rounded-round_to_list[2]), abs(values_to_be_rounded-round_to_list[3])]

# Turn into dataframe and transpose the data to set it up for the next part. 
# I name the headers the index value of round_to_list. 
# For example in the difference_df.iloc[0,0], that value is how far away 30 is from 15.
difference_df = pd.DataFrame(difference, index=[0, 1, 2, 3]).transpose()

# Get min value of each row
min_Value = difference_df.min(axis=1)

At this part I am trying to retrieve the header for each minimum value because that is the index I need to identify which value in my round_to_list I should round to.

Interpretation Example

The value for difference_df.iloc[0, 1], which is 0, is going to the min for that row. This means that the header, which is 1, is the index of round_to_list that I need to round to. In this example it would be 30.

2 Answers

Here you go:

data = pd.Series([30, 101, 20, 48, 60])
boundaries = pd.Series([15, 30, 60, 120])
print(data.map(lambda pt: boundaries[(boundaries - pt).abs().argmin()]))

Output:

0     30
1    120
2     15
3     60
4     60

Since your list of acceptable values is a geometric progression, I feel that rounding has to be done on the log scape (for small deviations, there will be no difference between this and the arithmetic rounding):

import numpy as np
BASE = 15
boundaries = pd.Series([15, 30, 60, 120])
((2 ** np.round(np.log2(values_to_be_rounded / BASE))) * BASE).astype(int)
#0    30
#1   120
#2    15
#3    60
#4    60

Incidentally, this approach is ~2.5 times faster than the one suggested by Balaji Ambresh. (Because map() is slow.)

Related