13

Given a month in numeric form (e.g., 2 for February), how do you find the first month of its respective quarter (e.g., 1 for January)?

I read through the datetime module documentation and the Pandas documentation of their datetime functions, which ought to be relevant, but I could not find a function that solves this problem.

Essentially, what I am trying to understand is how I could produce a function like the one below that, given month x, outputs the number corresponding to the first month of x's quarter.

>> first_month_quarter(5)
4
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Gyan Veda
  • 6,309
  • 11
  • 41
  • 66
  • 1
    related: [Is there a Python function to determine which quarter of the year a date is in?](http://stackoverflow.com/q/1406131/4279) – jfs Apr 03 '15 at 07:30

5 Answers5

21

It's not so pretty, but if speed is important a simple list lookup slaughters math:

def quarter(month, quarters=[None, 1, 1, 1, 4, 4, 4,
                             7, 7, 7, 10, 10, 10]):
    """Return the first month of the quarter for a given month."""
    return quarters[month]

A timeit comparison suggests this is about twice as fast as TigerhawkT3's mathematical approach.


Test script:

import math

def quarter(month, quarters=[None, 1, 1, 1, 4, 4, 4,
                             7, 7, 7, 10, 10, 10]):
    """Return the first month of the quarter for a given month."""
    return quarters[month]

def firstMonthInQuarter1(month):
    return (month - 1) // 3 * 3 + 1

def firstMonthInQuarter2(month):
    return month - (month - 1) % 3

def first_month_quarter(month):
    return int(math.ceil(month / 3.)) * 3 - 2

if __name__ == '__main__':
    from timeit import timeit
    methods = ['quarter', 'firstMonthInQuarter1', 'firstMonthInQuarter2',
               'first_month_quarter']
    setup = 'from __main__ import {}'.format(','.join(methods))
    results = {method: timeit('[{}(x) for x in range(1, 13)]'.format(method),
                              setup=setup)
               for method in methods}
    for method in methods:
        print '{}:\t{}'.format(method, results[method])

Results:

quarter:    3.01457574242
firstMonthInQuarter1:   4.51578357209
firstMonthInQuarter2:   4.01768559763
first_month_quarter:    8.08281871176
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 9
    It's always risky to make speed claims when answers may come along later. For ten million iterations, your method takes 7.2 seconds but the two in my answer are 7.0 and 7.3. In fact, even the `math` one clocks in at 7.6 so it's not so bad. Not saying your solution is no good (in fact there's little difference between the lot), just saying you may want to dial back a bit on the claims :-) In any case, thanks for educating me on `timeit`, I'd not seen that before – paxdiablo Apr 02 '15 at 13:40
  • @paxdiablo true! However, I've just re-run the tests, including your approaches, and seen similar results - using a list is more than twice as fast as the `math.ceil` and 25-50% faster than integer arithmetic (`timeit` defaults to 1,000,000 iterations). I've updated the answer with my testing and results. – jonrsharpe Apr 02 '15 at 14:15
19

It's a simple mapping function that needs to convert:

1 2 3 4 5 6 7 8 9 10 11 12
           |
           V
1 1 1 4 4 4 7 7 7 10 10 10

This can be done in a number of ways with integral calculations, two of which are:

def firstMonthInQuarter(month):
    return (month - 1) // 3 * 3 + 1

and:

def firstMonthInQuarter(month):
    return month - (month - 1) % 3

The first involves integer division of the month converted to a zero-based month to get the zero-based quarter, multiplication to turn that back into a zero-based month (but the month at the start of the quarter), then adding one again to make the range 1..12.

month  -1  //3  *3  +1
-----  --  ---  --  --
    1   0    0   0   1
    2   1    0   0   1
    3   2    0   0   1
    4   3    1   3   4
    5   4    1   3   4
    6   5    1   3   4
    7   6    2   6   7
    8   7    2   6   7
    9   8    2   6   7
   10   9    3   9  10
   11  10    3   9  10
   12  11    3   9  10

The second just subtracts the position within a quarter (0, 1, 2) from the month itself to get the starting month.

month(a)  -1  %3(b)  a-b
--------  --  -----  ---
       1   0      0    1
       2   1      1    1
       3   2      2    1
       4   3      0    4
       5   4      1    4
       6   5      2    4
       7   6      0    7
       8   7      1    7
       9   8      2    7
      10   9      0   10
      11  10      1   10
      12  11      2   10
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • would u be able to comment on the speed of all the three methods? – Rhythem Aggarwal Jun 22 '18 at 08:37
  • 1
    @RhythemAggarwal, for a start, there are only *two* methods I presented :-) In any case, the time difference between the two will be small enough as to not matter at all. Optimisations, unless you're needing to do things millions of times a second, are best reserved for macro things like data structure or algorithm selection. – paxdiablo Jun 22 '18 at 10:05
  • Oh okay, i am still new to python and was thinking if traversing a list would be slower that a math operation considerably!! Thanks very much – Rhythem Aggarwal Jun 22 '18 at 10:09
13
def first_month(month):
    return (month-1)//3*3+1

for i in range(1,13):
    print i, first_month(i)
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • This would have been my answer had I not been asleep at the time it was asked :-) Convert to zero-based month number and just use integer math. – paxdiablo Apr 01 '15 at 01:32
10

Here is an answer suggested by TigerhawkT3. Perhaps the leanest suggestion so far and, apparently, also the fastest.

import math

def first_month_quarter(month):
    return int(math.ceil(month / 3.)) * 3 - 2

For example:

>> first_month_quarter(5)
4
Community
  • 1
  • 1
Gyan Veda
  • 6,309
  • 11
  • 41
  • 66
  • 2
    The standard `math` module also has a `ceil` function that is functionally equivalent for this. – TigerhawkT3 Mar 31 '15 at 18:42
  • Good point! I've never really known how to choose between the `ceil` functions of `numpy` and `math`. Not sure how/when they differ in their efficiency and output. – Gyan Veda Mar 31 '15 at 18:44
  • 1
    You could also use `((month - 1) // 3 % 4) * 3 + 1`, which agrees for months between 1 and 12 (inclusive), and always returns one of the months 1, 4, 7, or 10 for `months` less than 1 or greater than 12. – unutbu Mar 31 '15 at 18:49
  • 2
    I would also recommend simplifying the return formula to `return 3*qrtr - 2` instead of `return qrtr + (qrtr-1) * 2`. It's more than twice as fast (tested), probably due to only having to get the value of `qrtr` once. Plus, then you don't have to save `qrtr`, turning the entire function into `return int(math.ceil(month/3.)) * 3 - 2`. If you switch to Python 3, you don't even have to call `int`, because Python 3's `ceil` already returns an `int`. Could be good if you have a lot of records to work with. – TigerhawkT3 Mar 31 '15 at 19:11
4

Packing a lookup table into a 64-bit literal:

def firstMonthOfQuarter(month):
    return (0x000aaa7774441110L >> (month << 2)) & 15
samgak
  • 23,944
  • 4
  • 60
  • 82