• The correct way to bin a pandas.DataFrame is to use pandas.cut
  • Verify the date column is in a datetime format with pandas.to_datetime.
  • Use .dt.hour to extract the hour, for use in the .cut method.
  • Tested in python 3.8.11 and pandas 1.3.1

How to bin the data

import pandas as pd
import numpy as np  # for test data
import random  # for test data

# setup a sample dataframe; creates 1.5 months of hourly observations
np.random.seed(365)
random.seed(365)
data = {'date': pd.bdate_range('2020-09-21', freq='h', periods=1100).tolist(),
        'x': np.random.randint(10, size=(1100))}
df = pd.DataFrame(data)

# the date column of the sample data is already in a datetime format
# if the date column is not a datetime, then uncomment the following line
# df.date= pd.to_datetime(df.date)

# define the bins
bins = [0, 6, 12, 18, 24]

# add custom labels if desired
labels = ['00:00-05:59', '06:00-11:59', '12:00-17:59', '18:00-23:59']

# add the bins to the dataframe
df['Time Bin'] = pd.cut(df.date.dt.hour, bins, labels=labels, right=False)

df.head()

                  date  x     Time Bin
0  2020-09-21 00:00:00  2  00:00-05:59
1  2020-09-21 01:00:00  4  00:00-05:59
2  2020-09-21 02:00:00  1  00:00-05:59
3  2020-09-21 03:00:00  5  00:00-05:59
4  2020-09-21 04:00:00  2  00:00-05:59

df.tail()

                    date  x     Time Bin
1095 2020-11-05 15:00:00  2  12:00-17:59
1096 2020-11-05 16:00:00  3  12:00-17:59
1097 2020-11-05 17:00:00  1  12:00-17:59
1098 2020-11-05 18:00:00  2  18:00-23:59
1099 2020-11-05 19:00:00  2  18:00-23:59

Groupby 'Time Bin'

  • Use pandas.DataFrame.groupby on 'Time Bin', and then aggregate 'x' into a list and mean.
# groupby Time Bin and aggregate a list for the observations, and mean
dfg = df.groupby('Time Bin', as_index=False)['x'].agg([list, 'mean'])

# change the column names, if desired
dfg.columns = ['X Observations', 'X mean']

dfg

                      X Observations    X mean
Time Bin                                 
00:00-05:59  [2, 4, 1, 5, 2, 2, ...]  4.416667
06:00-11:59  [9, 8, 4, 0, 3, 3, ...]  4.760870
12:00-17:59  [7, 7, 7, 0, 8, 4, ...]  4.384058
18:00-23:59  [3, 2, 6, 2, 6, 8, ...]  4.459559
Answer from Trenton McKinney on Stack Overflow
Top answer
1 of 5
21
  • The correct way to bin a pandas.DataFrame is to use pandas.cut
  • Verify the date column is in a datetime format with pandas.to_datetime.
  • Use .dt.hour to extract the hour, for use in the .cut method.
  • Tested in python 3.8.11 and pandas 1.3.1

How to bin the data

import pandas as pd
import numpy as np  # for test data
import random  # for test data

# setup a sample dataframe; creates 1.5 months of hourly observations
np.random.seed(365)
random.seed(365)
data = {'date': pd.bdate_range('2020-09-21', freq='h', periods=1100).tolist(),
        'x': np.random.randint(10, size=(1100))}
df = pd.DataFrame(data)

# the date column of the sample data is already in a datetime format
# if the date column is not a datetime, then uncomment the following line
# df.date= pd.to_datetime(df.date)

# define the bins
bins = [0, 6, 12, 18, 24]

# add custom labels if desired
labels = ['00:00-05:59', '06:00-11:59', '12:00-17:59', '18:00-23:59']

# add the bins to the dataframe
df['Time Bin'] = pd.cut(df.date.dt.hour, bins, labels=labels, right=False)

df.head()

                  date  x     Time Bin
0  2020-09-21 00:00:00  2  00:00-05:59
1  2020-09-21 01:00:00  4  00:00-05:59
2  2020-09-21 02:00:00  1  00:00-05:59
3  2020-09-21 03:00:00  5  00:00-05:59
4  2020-09-21 04:00:00  2  00:00-05:59

df.tail()

                    date  x     Time Bin
1095 2020-11-05 15:00:00  2  12:00-17:59
1096 2020-11-05 16:00:00  3  12:00-17:59
1097 2020-11-05 17:00:00  1  12:00-17:59
1098 2020-11-05 18:00:00  2  18:00-23:59
1099 2020-11-05 19:00:00  2  18:00-23:59

Groupby 'Time Bin'

  • Use pandas.DataFrame.groupby on 'Time Bin', and then aggregate 'x' into a list and mean.
# groupby Time Bin and aggregate a list for the observations, and mean
dfg = df.groupby('Time Bin', as_index=False)['x'].agg([list, 'mean'])

# change the column names, if desired
dfg.columns = ['X Observations', 'X mean']

dfg

                      X Observations    X mean
Time Bin                                 
00:00-05:59  [2, 4, 1, 5, 2, 2, ...]  4.416667
06:00-11:59  [9, 8, 4, 0, 3, 3, ...]  4.760870
12:00-17:59  [7, 7, 7, 0, 8, 4, ...]  4.384058
18:00-23:59  [3, 2, 6, 2, 6, 8, ...]  4.459559
2 of 5
8

Whenever I bin time series data by a time range, which seems to be what you are doing here, I just create an "hour of day" column and slice over that. Also, I normally set the index as datetime values...though that is not necessary here.

# assuming your "timestamp" column is labeled ts: 
df['hod'] = [r.hour for r in df.ts]

# now you can calculate stats for each bin
ave = df[ (df.hod>=0) & (df.hod<6) ].mean()

I would think there is a method of using df.resample here, but with the poorly defined starting/ending points in your time series I think this may require more attention than the above method.

Is this along the lines of what you were wanting?

🌐
Deephaven
deephaven.io › core › docs › reference › community-questions › bin-times-specific-time
How can I bin times to a specific time? | Deephaven
January 2, 2024 - The Deephaven query library's built-in DateTimeUtils class has the methods lowerBin and upperBin that can be used to bin your time series data. They allow you to pass an offset so that the bins start later than they normally would. You can use this offset to get the bins to start at a specific time.
Top answer
1 of 2
3

I think @DrV's is the correct answer, but I've prepared an example trying to show how something similar could be achieved using Pandas:

import numpy
import pandas
import datetime
import time

# Binning delta

delta = datetime.timedelta(hours=1)

# Sample data

sample = [
    ['2014-08-09 16:30:00', 'label1'],
    ['2014-08-09 15:30:00', 'label2'],
    ['2014-08-09 14:30:00', 'label3'],
    ['2014-08-09 14:00:00', 'label4']
]

# Create dataframe and append UNIX timestamp column

df = pandas.DataFrame(sample)
df.columns = ['Datetime', 'Label']
df['Datetime'] = pandas.to_datetime(df['Datetime'])
df['UnixStamp'] = df['Datetime'].apply(lambda d: time.mktime(d.timetuple()))
df = df.set_index('Datetime')

# Calculate bins

bins = numpy.arange(min(df['UnixStamp']), max(df['UnixStamp']) + delta.seconds, delta.seconds)

# Group columns by datetime bin

def bin_from_tstamp(tstamp):

    diffs = [abs(tstamp - bin) for bin in bins]
    return bins[diffs.index(min(diffs))]

grouped = df.groupby(df['UnixStamp'].map(
    lambda t: datetime.datetime.fromtimestamp(bin_from_tstamp(t))
))

At this point grouped contains the dataset grouped by datetime bins.

The following is the result of printing grouped.groups (where the keys are the datetime bins and the values are the grouped datetimes):

{
    numpy.datetime64('2014-08-09T18:00:00.000000000+0200'): [
        Timestamp('2014-08-09 16:30:00')
    ], 
    numpy.datetime64('2014-08-09T17:00:00.000000000+0200'): [
        Timestamp('2014-08-09 15:30:00')
    ], 
    numpy.datetime64('2014-08-09T16:00:00.000000000+0200'): [
        Timestamp('2014-08-09 14:30:00'), 
        Timestamp('2014-08-09 14:00:00'
    ]
}
2 of 2
2

Something along these lines should do:

# data: a lists of lists (length 2) of measurements
# res: resulting list of lists
# delta: time delta

# output list (will be a list of lists, as in the question

res = []
# end of first bin:
binstart = data[0][0]
res.append([binstart, []])

# iterate through the data item
for d in data:
    # if the data item belongs to this bin, append it into the bin
    if d[0] < binstart + delta:
        res[-1][1].append(d[1])
        continue

    # otherwise, create new empty bins until this data fits into a bin
    binstart += delta
    while d[0] > binstart + delta:
        res.append([binstart, [])
        binstart += delta

    # create a bin with the data
    res.append([binstart, [d[1]]])
🌐
GitHub
gist.github.com › jamesattard › 07ad6a706281c73a399595450b89eee2
Pandas binning over timeseries · GitHub
Pandas binning over timeseries. GitHub Gist: instantly share code, notes, and snippets.
Find elsewhere
Top answer
1 of 3
3

I know it's late. But better late than never. I also came across a similar requirement and done by using pandas library.

  • First, Load data in pandas data-frame

  • Second, check TIME column must be datetime object and not object type (like string or whatever). You can check it by

    df.info()

for example, in my case TIME column was initially of object type i.e. string type

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 17640 entries, 0 to 17639
Data columns (total 3 columns):
TIME           17640 non-null object
value          17640 non-null int64
dtypes: int64(1), object(2)
memory usage: 413.5+ KB
  • if that is the case, then convert it to pandas datetime object by using this command

    df['TIME'] = pd.to_datetime(df['TIME']) ignore this if already in datetime format

df.info() now gives updated format

 <class 'pandas.core.frame.DataFrame'>
 RangeIndex: 17640 entries, 0 to 17639
 Data columns (total 3 columns):
 TIME           17640 non-null datetime64[ns]
 value          17640 non-null int64
 dtypes: datetime64ns, int64(1)
 memory usage: 413.5 KB
  • Now our dataframe is ready for magic :)

       counts = pd.Series(index=df.TIME, data=np.array(df.count)).resample('15T').count()
       print(counts[:3])
    
     TIME
     2017-07-01 00:00:00    3
     2017-07-01 00:15:00    3
     2017-07-01 00:30:00    3
     Freq: 15T, dtype: int64
    

    in above command 15T means 15minutes bucket, you can replace it with D for day bucket, 2D for 2 days bucket, M for month bucket, 2M for 2 months bucket and so on. You can read the detail of these notations on this link

  • now, our buckets data is done as you can see above. for time range use this command. Use the same time range as of data. In my case, my data was 3 months so I am creating time-range of 3 months.

 r = pd.date_range('2017-07', '2017-09', freq='15T')
 x = np.repeat(np.array(r), 2, axis=0)[1:-1]
 # now reshape data to fit in Dataframe
 x = np.array(x)[:].reshape(-1, 2)
 # now fit in dataframe and print it
 final_df = pd.DataFrame(x, columns=['start', 'end'])
 print(final_df[:3])
                  start                 end
0   2017-07-01 00:00:00 2017-07-01 00:15:00
1   2017-07-01 00:15:00 2017-07-01 00:30:00
2   2017-07-01 00:30:00 2017-07-01 00:45:00

date ranges also done

  • Now append count and dateranges to get final outcome

     final_df['count'] = np.array(means)
     print(final_df[:3])
    
                  start                 end count
0   2017-07-01 00:00:00 2017-07-01 00:15:00     3
1   2017-07-01 00:15:00 2017-07-01 00:30:00     3
2   2017-07-01 00:30:00 2017-07-01 00:45:00     3

Hope anyone find it useful.

2 of 3
1

Well, I'm not sure that this is what you asked for. If it's not, I would recommend you to improve your question, because it's very hard to understand your problem. In particular, it would be nice to see what you've already tried to do.

from __future__ import division, print_function
from collections import namedtuple
from itertools import product
from datetime import time
from StringIO import StringIO


MAX_HOURS = 23
MAX_MINUTES = 59


def process_data_file(data_file):
    """
    The data_file is supposed to be an opened file object
    """
    time_entry = namedtuple("time_entry", ["time", "count"])
    data_to_bin = []
    for line in data_file:
        t, count = line.rstrip().split("\t")
        t = map(int, t.split()[-1].split(":")[:2])
        data_to_bin.append(time_entry(time(*t), int(count)))
    return data_to_bin


def make_milestones(min_hour=0, max_hour=MAX_HOURS, interval=15):
    minutes = [minutes for minutes in xrange(MAX_MINUTES+1) if not minutes % interval]
    hours = range(min_hour, max_hour+1)
    return [time(*milestone) for milestone in list(product(hours, minutes))]


def bin_time(data_to_bin, milestones):
    time_entry = namedtuple("time_entry", ["time", "count"])
    data_to_bin = sorted(data_to_bin, key=lambda time_entry: time_entry.time, reverse=True)
    binned_data = []
    current_count = 0
    upper = milestones.pop()
    lower = milestones.pop()
    for entry in data_to_bin:
        while not lower <= entry.time <= upper:
            if current_count:
                binned_data.append(time_entry("{}-{}".format(str(lower)[:-3], str(upper)[:-3]), current_count))
                current_count = 0
            upper, lower = lower, milestones.pop()
        current_count += entry.count
    return binned_data


data_file = StringIO("""1-1-1900 10:41:00\t1
3-1-1900 09:54:00\t1
4-1-1900 15:45:00\t1
5-1-1900 18:41:00\t1
4-1-1900 15:45:00\t1""")


binned_time = bin_time(process_data_file(data_file), make_milestones())
for entry in binned_time:
    print(entry.time, entry.count, sep="\t")

The output:

18:30-18:45 1
15:45-16:00 2
10:30-10:45 1
🌐
Stack Overflow
stackoverflow.com › questions › 72166499 › bin-rows-by-time-with-pandas
python - Bin rows by time with pandas - Stack Overflow
May 9, 2022 - So this may seem like a simple question, but every question I've checked isn't exactly approaching the problem in the same way I am. I'm trying to bin the timestamps of a dataframe into specific bu...
🌐
Python Course
python-course.eu › numerical-programming › binning-in-python-and-pandas.php
34. Binning in Python and Pandas | Numerical Programming
February 3, 2025 - We used an IntervalIndex as a bin for binning the weight data. The function "cut" can also cope with two other kinds of bin representations: an integer: defining the number of equal-width bins in the range of the values "x". The · range of "x" is extended by .1% on each side to include the minimum and maximum values of "x".
🌐
Practical Business Python
pbpython.com › pandas-qcut-cut.html
Binning Data with Pandas qcut and cut - Practical Business Python
Pandas qcut and cut are both used to bin continuous values into discrete buckets or bins. This article explains the differences between the two commands and how to use each.
🌐
Stack Overflow
stackoverflow.com › questions › 56915254 › pandas-binning-number-of-minutes-in-various-datetime-ranges
python - Pandas- binning number of minutes in various datetime ranges - Stack Overflow
July 6, 2019 - Then I would build a list of sub-dataframes by iterating once the dataframe values in order to add all the bins corresponding to a row and would concat that list. From there it it enough to compute the time interval per bin and use pivot_table to get the expected result.
🌐
Train in Data
blog.trainindata.com › master-data-binning-in-python-using-pandas
Master Data Binning in Python using Pandas | Train in Data Blog
February 23, 2023 - Binning data, sometimes also referred to as bucketing, is also useful in data science and machine learning projects, as it reduces the training time of decision tree-based algorithms by reducing the number of cut-points they examine during the induction (training process). In this tutorial, we’ll look into binning data in Python using the cut and qcut functions from the open-source library pandas.
🌐
JMP User Community
community.jmp.com › t5 › Discussions › bin-each-row-based-on-date-time › td-p › 281242
bin each row based on date time - JMP User Community
June 10, 2023 - However, this is kind of troublesome for the data time. let say I am binning based before 7/22/2020 12:00:00pm as Good and after as bad. I can use the IF formula logic to compare the date time to 7/22/2020 12:00:00pm. But to do that I need to convert to the numeric value of 3678264000.
🌐
Stack Overflow
stackoverflow.com › questions › 75598405 › function-for-binning-data-based-on-date-values
python - function for binning data based on date values - Stack Overflow
February 28, 2023 - The way in which the data is binned depends upon the total number of months in the data set. The logic of this 'binning' of the data is as follows: * if the total number of months is > 48, the data is binned by ...