4

I would like to choose a random month between the current year and 2016. This is my current, very naive, solution

from random import choice
def get_month():
    return choice({'2018-06','2018-05','2018-04','2018-03'})

It's obvious that this set will get too large in the future so what would be a better way of achieving this?

niraj
  • 17,498
  • 4
  • 33
  • 48
jinyus
  • 477
  • 7
  • 19
  • In what sense do you require randomness? February is shorter than March. Should February have `~28.25/365.25` probability of being chosen? Or should each month be given a `1/12` chance of being chosen? What about the current month? Should this month have a `16/365.25` probability of being chosen? – Mateen Ulhaq Jun 17 '18 at 04:12
  • Each month should have 1/12 chance of being chosen. – jinyus Jun 17 '18 at 05:05

8 Answers8

5

May be you can have two list of month and year since, these will be fixed. Then, you can randomly choose between each and make a date to return. That way I think then no need to generate all different dates and no need to store in large list:

from random import choice
def get_month():
    months = list(range(1, 13)) # 12 months list
    year = list(range(2016, 2019)) # years list here
    val = '{}-{:02d}'.format(choice(year), choice(months))
    return val

get_month()

Result:

'2017-05'

Update

Just in case if there is limitation on not exceeding current month if the year selected is current year then, you may need if condition for generating list of months as below:

from random import choice
from datetime import datetime

def get_month():

    today = datetime.today() # get current date
    year = list(range(2016, today.year+1)) # list till current date year

    # randomly select year and create list of month based on year
    random_year = choice(year)

    # if current year then do not exceed than current month
    if random_year == today.year:
        months = list(range(1, today.month+1))
    else:
        # if year is not current year then it is less so can create 12 months list
        months = list(range(1, 13)) 

    val = '{}-{:02d}'.format(random_year, choice(months))

    return val
niraj
  • 17,498
  • 4
  • 33
  • 48
  • @MadPhysicist I thought it would not exceed current month and current year. Is there something wrong? – niraj Jun 17 '18 at 03:54
  • *Some months are bigger than others. Some years' months are bigger than other years' months.* – Mateen Ulhaq Jun 17 '18 at 03:55
  • @MateenUlhaq I am not quite sure I understand and I may be missing point on random selection. But does it mean number of days in months? I thought it would have 12 months if year is past or else till current month. – niraj Jun 17 '18 at 04:02
  • 1
    It depends on what distribution OP really is looking for here. (For what purpose/application?) My point was that for proper uniformity in date, February should have (on average) a `~28.25/365.25` chance of being chosen. This percentage depends on the year (among possibly other factors). – Mateen Ulhaq Jun 17 '18 at 04:16
  • The months elapsed in the current year should have a higher probability of being selected. – Mad Physicist Jun 17 '18 at 05:47
2

You can use the library pandas and use date_range

choice(pd.date_range(start="2016-01-01", end="2018-01-01", freq='M'))

If you want it until today, you can just substitute the startand end arguments for whatever suitable, e.g.

from dateutil.relativedelta import relativedelta
today = datetime.today()
two_years_ago = today - relativedelta(years=2)
choice(pd.date_range(start=two_years_ago, end=today, freq='M'))
rafaelc
  • 57,686
  • 15
  • 58
  • 82
  • Would "today - relativedelta(years=2)" include Jan. 2016 or just the last 24months? Do you think it would be better to hardcode the start date and avoid importing relativedelta? – jinyus Jun 17 '18 at 04:22
  • It depends on your intentions. If you really want *always* from jan 2016 til today, I think you can hardcode that. If you always want an exact 2-year time frame, then relative delta is recommended :) – rafaelc Jun 17 '18 at 04:23
2

I wish my code will give you some inspiration.

 import random
 import datetime

 def RandomMonth(st_year, st_month, ed_year, ed_month):
     """st_year, st_month and ed_year/month is int. """
     start_date = datetime.datetime(st_year, st_month, 1)
     end_date = datetime.datetime(ed_year, ed_month, 31)
     d_days = (end_date - start_date).days
     r_days = random.randint(0, ttl_days)
     r_date = start_date + datetime.timedelta(days = r_days)
     return r_date.strftime("%B/%Y")
Handymax
  • 21
  • 3
1

You've identified the issue correctly. While the set of discrete items can get unwieldy pretty quickly (actually it won't), selecting a random integer over an arbitrary range is easy.

You should be able to compute the number of months between your dates of interest easily. That means that you have a trivial 1-to-1 mapping from months to integers. Now you can do

m = random.randrange(N)

where N is your total number of months (number of current month + 12 * number of whole years). Mapping back to months is easy using Python's datetime API:

origin = datetime.date(2016, 1, 1)
today = datetime.today()
n = (today.year - origin.year) * 12 + today.month - origin.month

m = random.randrange(n)
x = origin.replace(year=origin.year + m // 12, month=origin.month + m % 12)

Month delta (n) based on this answer and month increment (x) based on this answer. Both computations will become slightly more complicated if you move away from January for the origin.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
1

just pick a random 1-24 then apply some date arithmetic to land on your month from the current date.

from datetime import datetime, date
from dateutil.relativedelta import relativedelta
from random import randint

refdate = date.today() #datetime.now() works too!
for i in range(0,10):
    #this will take off at least 1 month and 24 will return 2016-06 from 2018-06
    months = randint(1,24)  
    back = refdate - relativedelta(months=months)
    print ("start:%s - %02d months => %s" % (refdate, months, back))

output:

start:2018-06-17 - 24 months => 2016-06-17
start:2018-06-17 - 22 months => 2016-08-17
start:2018-06-17 - 21 months => 2016-09-17
start:2018-06-17 - 08 months => 2017-10-17
start:2018-06-17 - 16 months => 2017-02-17
start:2018-06-17 - 06 months => 2017-12-17
start:2018-06-17 - 07 months => 2017-11-17
start:2018-06-17 - 14 months => 2017-04-17
start:2018-06-17 - 07 months => 2017-11-17
start:2018-06-17 - 07 months => 2017-11-17
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
0

you can do them seperately like take random year and take random month, then put it together

def get_month():
    year = random.choice([2016, 2017, 2018])
    month = random.choice(range(1,13))
    new_date = str(year) + "-" + str(month)
    return new_date
Bibek Bhandari
  • 422
  • 1
  • 4
  • 15
0

Try using calendar:

from random import choice
import calendar
def get_month():
    l = []
    y = 0
    for i in range(2016,2019):
        l.append(str(i)+'-'+str(calendar.monthrange(i,12)[0]))
    return choice(l)
print(get_month())
U13-Forward
  • 69,221
  • 14
  • 89
  • 114
0

I think I came with a lighter solution than the one using pandas

current_month = datetime.today().month
current_year = datetime.today().year 
start_year = 2016

#total months between now and the start year
total_months = current_month + (current_year - start_year)*12 

months = []
month_count = 1
year_count = start_year

#iterate through the list, when it reaches 13, increment year_count and reset the month_count to 1
for month in range(total_months):
    if month_count<13:
        months.append(str(year_count) + "-" + str("{0:0=2d}".format(month_count)))
        month_count+=1
    if month_count == 13:
        year_count+=1
        month_count=1

results

['2016-01', '2016-02', '2016-03', '2016-04', '2016-05', '2016-06', '2016-07', '2016-08', '2016-09', '2016-10', '2016-11', '2016-12', '2017-01', '2017-02', '2017-03', '2017-04', '2017-05', '2017-06', '2017-07', '2017-08', '2017-09','2017-10', '2017-11', '2017-12', '2018-01', '2018-02', '2018-03', '2018-04', '2018-05', '2018-06']

for some reason, when I replace "if month_count == 13" with "else", it only goes up to 2018-04

I would appreciate some feedback on this solution.

jinyus
  • 477
  • 7
  • 19