Je recherche une fonction pour prendre 2 dates (admission et sortie) et un exercice comptable et renvoyer le nombre de jours dans chaque mois entre ces dates également.
L'exercice financier va du 1er avril -> 31 -Mars
J'ai actuellement une solution (ci-dessous) qui est un gâchis de SPSS et Python, en fin de compte, elle devra être réimplémentée dans SPSS, mais comme une fonction Python beaucoup plus soignée, cela signifie malheureusement qu'elle ne peut utiliser des bibliothèques standard (pas Pandas).
par exemple
* Count the beddays. * Similar method to that used in Care homes. * 1) Declare an SPSS macro which will set the beddays for each month. * 2) Use python to run the macro with the correct parameters. * This means that different month lengths and leap years are handled correctly. Define !BedDaysPerMonth (Month = !Tokens(1) /MonthNum = !Tokens(1) /DaysInMonth = !Tokens(1) /Year = !Tokens(1)) * Store the start and end date of the given month. Compute #StartOfMonth = Date.DMY(1, !MonthNum, !Year). Compute #EndOfMonth = Date.DMY(!DaysInMonth, !MonthNum, !Year). * Create the names of the variables e.g. April_beddays and April_cost. !Let !BedDays = !Concat(!Month, "_beddays"). * Create variables for the month. Numeric !BedDays (F2.0). * Go through all possibilities to decide how many days to be allocated. Do if keydate1_dateformat LE #StartOfMonth. Do if keydate2_dateformat GE #EndOfMonth. Compute !BedDays = !DaysInMonth. Else. Compute !BedDays = DateDiff(keydate2_dateformat, #StartOfMonth, "days"). End If. Else if keydate1_dateformat LE #EndOfMonth. Do if keydate2_dateformat GT #EndOfMonth. Compute !BedDays = DateDiff(#EndOfMonth, keydate1_dateformat, "days") + 1. Else. Compute !BedDays = DateDiff(keydate2_dateformat, keydate1_dateformat, "days"). End If. Else. Compute !BedDays = 0. End If. * Months after the discharge date will end up with negatives. If !BedDays < 0 !BedDays = 0. !EndDefine. * This python program will call the macro for each month with the right variables. * They will also be in FY order. Begin Program. from calendar import month_name, monthrange from datetime import date import spss #Set the financial year, this line reads the first variable ('year') fin_year = int((int(spss.Cursor().fetchone()[0]) // 100) + 2000) #This line generates a 'dictionary' which will hold all the info we need for each month #month_name is a list of all the month names and just needs the number of the month #(m < 4) + 2015 - This will set the year to be 2015 for April onwards and 2016 other wise #monthrange takes a year and a month number and returns 2 numbers, the first and last day of the month, we only need the second. months = {m: [month_name[m], (m < 4) + fin_year, monthrange((m < 4) + fin_year, m)[1]] for m in range(1,13)} print(months) #Print to the output window so you can see how it works #This will make the output look a bit nicer print("\n\n***This is the syntax that will be run:***") #This loops over the months above but first sorts them by year, meaning they are in correct FY order for month in sorted(months.items(), key=lambda x: x[1][1]): syntax = "!BedDaysPerMonth Month = " + month[1][0][:3] syntax += " MonthNum = " + str(month[0]) syntax += " DaysInMonth = " + str(month[1][2]) syntax += " Year = " + str(month[1][1]) + "." print(syntax) spss.Submit(syntax) End Program.
Associé - Comment calculer le nombre de jours entre deux dates données?
Solution actuelle (code SPSS)
+-----------------+-----------------+------+--+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | Admission | Discharge | FY | | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec | Jan | Feb | Mar | +-----------------+-----------------+------+--+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | 01 January 2017 | 05 January 2017 | 1617 | | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 4 | 0 | 0 | | 01 January 2017 | 05 June 2017 | 1617 | | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 31 | 28 | 31 | | 01 January 2017 | 05 June 2017 | 1718 | | 30 | 31 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | 01 January 2017 | 01 January 2019 | 1718 | | 30 | 31 | 30 | 31 | 31 | 30 | 31 | 30 | 31 | 31 | 28 | 31 | +-----------------+-----------------+------+--+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
5 Réponses :
Tout d'abord, je suggère d'utiliser des instances datetime.date
, afin que vous puissiez analyser vos dates à l'avance avec quelque chose comme ceci:
>>> d1 = datetime.date(2018, 2, 5) >>> d2 = datetime.date(2019, 1, 17) >>> r = f(d1, d2, '1718') >>> for k, v in sorted(r.items()): ... print(k, v) 2018-02 24 2018-03 31 >>> r = f(d1, d2, '1819') >>> for k, v in sorted(r.items()): ... print(k, v) 2018-04 30 2018-05 31 2018-06 30 2018-07 31 2018-08 31 2018-09 30 2018-10 31 2018-11 30 2018-12 31 2019-01 17
Ensuite, vous pouvez utiliser quelque chose comme ceci pour itérer sur la plage de dates:
import datetime import collections def f(start_date, end_date, fy_str): # if the date range falls outside the financial year, cut it off fy_start = datetime.date(2000 + int(fy_str[:2]), 4, 1) if start_date < fy_start: start_date = fy_start fy_end = datetime.date(2000 + int(fy_str[2:]), 3, 31) if end_date > fy_end: end_date = fy_end month_dict = collections.defaultdict(int) date = start_date while date <= end_date: # the key holds year and month to make sorting easier key = '{}-{:02d}'.format(date.year, date.month) month_dict[key] += 1 date += datetime.timedelta(days=1) return month_dict
L'utilisation serait comme ceci:
import datetime date = datetime.datetime.strptime('17-Jan-2018', '%d-%b-%Y').date()
La seule façon dont je peux penser à faire cela est de parcourir chaque jour et d'analyser le mois auquel il appartient:
import time, collections SECONDS_PER_DAY = 24 * 60 * 60 def monthlyBedDays(admission, discharge, fy=None): start = time.mktime(time.strptime(admission, '%d-%b-%Y')) end = time.mktime(time.strptime( discharge, '%d-%b-%Y')) if fy is not None: fy = str(fy) start = max(start, time.mktime(time.strptime('01-Apr-'+fy[:2], '%d-%b-%y'))) end = min(end, time.mktime(time.strptime('31-Mar-'+fy[2:], '%d-%b-%y'))) days = collections.defaultdict(int) for day in range(int(start), int(end) + SECONDS_PER_DAY, SECONDS_PER_DAY): day = time.localtime(day) key = time.strftime('%Y-%m', day) # use '%b' to answer the question exactly, but that's not such a good idea days[ key ] += 1 return days output = monthlyBedDays(admission="01-Jan-2018", discharge="25-Apr-2018") print(output) # Prints: # defaultdict(<class 'int'>, {'2018-01': 31, '2018-02': 28, '2018-03': 31, '2018-04': 25}) print(monthlyBedDays(admission="01-Jan-2018", discharge="25-Apr-2018", fy=1718)) # Prints: # defaultdict(<class 'int'>, {'2018-01': 31, '2018-02': 28, '2018-03': 31}) print(monthlyBedDays(admission="01-Jan-2018", discharge="25-Apr-2018", fy=1819)) # Prints: # defaultdict(<class 'int'>, {'2018-04': 25})
Notez que la sortie est un defaultdict code> tel que, si vous lui demandez le nombre de jours dans un mois (ou pour n'importe quelle clé) qui n'a pas été enregistré (par exemple
). output ['1999-12']
) il renverra 0. Notez également que j'ai utilisé le format '% Y-% m'
pour les clés de sortie. Cela rend beaucoup plus facile de trier la sortie et de lever l'ambiguïté entre les mois qui se produisent dans différentes années, que si vous utilisez le type de clé que vous avez initialement demandé ( '% b'
-> 'Jan'
Merci beaucoup, oui, je pense que je peux gérer le FY de cette question. Pourquoi utilisez-vous SECONDS_PER_DAY? Est-ce que datetime.timedelta (jours = 1) (d'après la réponse de Ralf serait euqivalent?
Vous pouvez utiliser datetime
au lieu de time
- ce serait l'approche la plus moderne, mais les deux fonctionnent et je suis juste plus familier avec time
.
@Moohan maintenant que je comprends que vous êtes au Royaume-Uni (et que je viens d'apprendre de wikipedia que la fin d'année d'entreprise est différente de quelques jours de la fin d'année personnelle dont je me souviens toujours ...) je peux voir que cette approche rend le fy
chose assez simple (voir edit).
Voici ma proposition de solution. D'après ce que je comprends, vous voulez le nombre de jours dans chaque mois entre les deux dates données. Je n'ai pas formaté les mois (je les ai laissés sous forme de nombres), mais cela devrait être assez facile pour vous de le faire.
from datetime import date from calendar import monthrange from dateutil.relativedelta import * #start and end dates d0 = date(2008, 8, 18) d1 = date(2008, 12, 26) delta = d1 - d0 delta_days = delta.days #number of days between the two dates #we create a copy of the start date so we can use it to iterate (so as to not to lose the initial date) curr_d = d0 while(1): #we iterate over each month until we have no days left #if theere are more days in delta_days than in the month #the number of days in the current month is the maximum number of days in that month if delta_days > monthrange(curr_d.year, curr_d.month)[1]: number_of_days_in_curr_month = monthrange(curr_d.year, curr_d.month)[1] delta_days -= monthrange(curr_d.year, curr_d.month)[1] #the delta_days is smaller than the maximum days in the current month #the number of days in the current month is thus == to delta_days #we exit the while loop here else: number_of_days_in_curr_month = delta_days print('month number: ' + str(curr_d.month) + ', year: ' + str(curr_d.year) + ', days: ' + str(number_of_days_in_curr_month) ) break print('month number: ' + str(curr_d.month) + ', year: ' + str(curr_d.year) + ', days: ' + str(number_of_days_in_curr_month) ) #we increment the current month curr_d = curr_d + relativedelta(months=+1)
Je pense que beaucoup de gens ont répondu avant que le PO ne donne les informations cruciales sur la façon dont fy
joue un rôle dans la fonction (modifier: beaucoup de gens ont lu cette modification et maintenant leurs réponses sont mis à jour également). OP souhaite connaître le nombre de jours entre admission
et décharge
qui atterrit au cours de l'exercice (1819 étant du 01-avril-2018 au 31-mars-2019). Et évidemment, comme tout le monde le sait, le nombre de jours doit être divisé par mois calendaire.
from datetime import datetime, timedelta # Function taken from https://stackoverflow.com/a/13565185/9462009 def lastDateOfThisMonth(any_day): next_month = any_day.replace(day=28) + timedelta(days=4) return next_month - timedelta(days=next_month.day) def monthlyBeddays(admission, discharge, fy): startFy = datetime.strptime('01-Apr-'+fy[:2], '%d-%b-%y') endFy = datetime.strptime('01-Apr-'+fy[2:], '%d-%b-%y') admissionDate = datetime.strptime(admission, '%d-%b-%Y') dischargeDate = datetime.strptime(discharge, '%d-%b-%Y') monthDates = {'Jan':0,'Feb':0,'Mar':0,'Apr':0,'May':0,'Jun':0,'Jul':0,'Aug':0,'Sep':0,'Oct':0,'Nov':0,'Dec':0} # if admitted after end of fy or discharged before beginning of fy, zero days counted if admissionDate > endFy or dischargeDate < startFy: return monthDates if admissionDate < startFy: # Jump ahead to start at the first day of fy if admission was prior to the beginning of fy now = startFy else: # If admission happened at or after the first day of fy, we begin counting from the admission date now = admissionDate while True: month = datetime.strftime(now,'%b') lastDateOfMonth = lastDateOfThisMonth(now) if now >= endFy: # If now is greater or equal to the first day of the next fy (endFy), we don't care about any of the following dates within the adm/dis date range break if month == datetime.strftime(dischargeDate,'%b') and datetime.strftime(now, '%Y') == datetime.strftime(dischargeDate, '%Y') and now >= startFy: # If we reach the discharge month, we count this month and we're done monthDates[month] = (dischargeDate - now).days # not adding one since in your example it seemed like you did not want to count the dischargeDate (Mar:4) break elif now < startFy: # If now is less than the first day of this fy (startFy), we move on from this month to the next month until we reach this fy pass else: # We are within this fy and have not reached the discharge month yet monthDates[month] = (lastDateOfMonth - now).days + 1 month = datetime.strftime(now, '%b') now = lastDateOfMonth + timedelta(days=1) # advance to the 1st of the next month return monthDates # Passes all six scenarios # Scenario #1: admitted before fy, discharged before fy (didn't stay at all during fy) print(monthlyBeddays("01-Jan-2018", "30-Mar-2018", '1819')) # {'Jan': 0, 'Feb': 0, 'Mar': 0, 'Apr': 0, 'May': 0, 'Jun': 0, 'Jul': 0, 'Aug': 0, 'Sep': 0, 'Oct': 0, 'Nov': 0, 'Dec': 0} # Scenario #2: admitted before fy, discharged during fy print(monthlyBeddays("01-Jan-2018", "30-May-2018", '1819')) # {'Jan': 0, 'Feb': 0, 'Mar': 0, 'Apr': 30, 'May': 29, 'Jun': 0, 'Jul': 0, 'Aug': 0, 'Sep': 0, 'Oct': 0, 'Nov': 0, 'Dec': 0} # Scenario #3: admitted during fy, discharged during fy print(monthlyBeddays("15-Apr-2018", "30-May-2018", '1819')) # {'Jan': 0, 'Feb': 0, 'Mar': 0, 'Apr': 16, 'May': 29, 'Jun': 0, 'Jul': 0, 'Aug': 0, 'Sep': 0, 'Oct': 0, 'Nov': 0, 'Dec': 0} # Scenario #4: admitted during fy, discharged after fy print(monthlyBeddays("15-Apr-2018", "30-May-2019", '1819')) # {'Jan': 31, 'Feb': 28, 'Mar': 31, 'Apr': 16, 'May': 31, 'Jun': 30, 'Jul': 31, 'Aug': 31, 'Sep': 30, 'Oct': 31, 'Nov': 30, 'Dec': 31} # Scenario #5: admitted before fy, discharged after fy (stayed the whole fy) print(monthlyBeddays("15-Mar-2018", "30-May-2019", '1819')) # {'Jan': 31, 'Feb': 28, 'Mar': 31, 'Apr': 30, 'May': 31, 'Jun': 30, 'Jul': 31, 'Aug': 31, 'Sep': 30, 'Oct': 31, 'Nov': 30, 'Dec': 31} # Scenario #6: admitted after fy, discharged after fy (didn't stay at all during fy) print(monthlyBeddays("15-Mar-2018", "30-May-2019", '1718')) # {'Jan': 0, 'Feb': 0, 'Mar': 17, 'Apr': 0, 'May': 0, 'Jun': 0, 'Jul': 0, 'Aug': 0, 'Sep': 0, 'Oct': 0, 'Nov': 0, 'Dec': 0}
Merci pour toutes les bonnes réponses. J'ai essayé d'implémenter certains d'entre eux dans SPSS, mais cela devient rapidement très compliqué et difficile d'essayer de passer des valeurs entre les deux ...
J'ai mis au point une fonction ordonnée pour analyser les variables de date SPSS en objets datetime Python: p>
from calendar import month_name import spss #Loop through the months by number in FY order for month in (4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3): #To show what is happening print some stuff to the screen print(month, month_name[month]) #Set up the syntax syntax = "!BedDaysPerMonth Month_abbr = " + month_name[month][:3] #print the syntax to the screen print(syntax) #run the syntax spss.Submit(syntax)
En ce qui concerne le problème principal, après un peu de réflexion, j'ai réussi à simplifier (je pense) et à améliorer la robustesse de ma solution d'origine.
Define !BedDaysPerMonth (Month_abbr = !Tokens(1) /AdmissionVar = !Default(keydate1_dateformat) !Tokens(1) /DischargeVar = !Default(keydate2_dateformat) !Tokens(1) /DelayedDischarge = !Default(0) !Tokens(1)) * Compute the month number from the name abbreviation. Compute #MonthNum = xdate.Month(Number(!Quote(!Concat(!Month_abbr, "-00")), MOYR6)). * Find out which year we need e.g for FY 1718: Apr - Dec = 2018, Jan - Mar = 2018. Do if (#MonthNum >= 4). Compute #Year = !Concat("20", !substr(!Unquote(!Eval(!FY)), 1, 2)). Else. Compute #Year = !Concat("20", !substr(!Unquote(!Eval(!FY)), 3, 2)). End if. * Now we have the year work out the start and end dates for the month. Compute #StartOfMonth = Date.DMY(1, #MonthNum, #Year). Compute #EndOfMonth = Date.DMY(1, #MonthNum + 1, #Year) - time.days(1). * Set the names of the variable for this month e.g. April_beddays. * And then create the variable. !Let !BedDays = !Concat(!Month_abbr, "_beddays"). Numeric !BedDays (F2.0). * Go through all possibilities to decide how many days to be allocated. Do if !AdmissionVar LE #StartOfMonth. Do if !DischargeVar GT #EndOfMonth. * They were in hospital throughout this month. * This will be the maximum number of days in the month. Compute !BedDays = DateDiff(#EndOfMonth, #StartOfMonth, "days") + 1. Else if !DischargeVar LE #StartOfMonth. * The whole record occurred before the month began. Compute !BedDays = 0. Else. * They were discharged at some point in the month. Compute !BedDays = DateDiff(!DischargeVar, #StartOfMonth, "days"). End If. * If we're here they were admitted during the month. Else if !AdmissionVar LE #EndOfMonth. Do if !DischargeVar GT #EndOfMonth. Compute !BedDays = DateDiff(#EndOfMonth, !AdmissionVar, "days") + 1. Else. * Admitted and discharged within this month. Compute !BedDays = DateDiff(!DischargeVar, !AdmissionVar, "days"). End If. Else. * They were admitted in a future month. Compute !BedDays = 0. End If. * If we are looking at Delayed Discharge records, we should count the last day and not the first. * We achieve this by taking a day from the first month and adding it to the last. !If (!DelayedDischarge = 1) !Then Do if xdate.Month(!AdmissionVar) = xdate.Month(date.MOYR(#MonthNum, #Year)) and xdate.Year(!AdmissionVar) = #Year. Compute !BedDays = !BedDays - 1. End if. Do if xdate.Month(!DischargeVar) = xdate.Month(date.MOYR(#MonthNum, #Year)) and xdate.Year(!DischargeVar) = #Year. Compute !BedDays = !BedDays + 1. End if. !ifEnd. * Tidy up the variable. Variable Width !Beddays (5). Variable Level !Beddays (Scale). !EndDefine.
Double possible de Comment calculer le nombre de jours entre deux données dates?
Regardez le module
datetime
pour une représentation correcte des dates, etdateutil
pour une analyse robuste des chaînes en dates.@Moohan quel serait le but du troisième paramètre, l'année? Les dates de début et de fin semblent déjà inclure l'année.
@JonahBishop pas un double. OP veut des jours chaque mois .
À quoi sert
fy
s'il existe déjà des informations sur l'année où il se trouve dans les datesadmission
etsortie
?Que voulez-vous faire s'il y a deux janvier entre les deux dates indiquées? Vous vous retrouvez avec Jan: 62 ou quelque chose comme ça? Ou les séparer par année?
@Endyd Le but de fy est de traiter des dates qui traversent plusieurs exercices financiers - considérez 01-avril-2015 -> 10-avril-2017 - si l'exercice était 1516 ou 1617 tous les mois auraient des jours maximum , si l'exercice était 1718, avril aurait 9 et tous les autres mois 0.
@jonsharpe actuellement, j'ai une solution de travail mais c'est un gâchis de SPSS et de Python, je voulais réécrire dans une fonction Python soignée pour la rendre plus maintenable et facile à lire - actuellement en train d'écrire quelque chose, je le partagerai dès qu'il fera quelque chose. .. si j'arrive aussi loin!
Le 1er avril est-il le début d'un nouvel exercice?
utilisez-vous des dates avec des années mélangées à l'année que vous voulez calculer? Je suis confus.