def makeFit(inValByYear, maxSize, verbose=False): retValByYear = {} #Python2: for (year, value) in inValByYear.iteritems(): for (year, value) in inValByYear.items(): if value != None: retValByYear[year] = value retYearByVal = reverseDict(retValByYear) # Remove the oldest year that is not the max or min value. while len(retValByYear) > maxSize: years = sorted(retValByYear.keys()) firstBoringYearIndex = 0 while (firstBoringYearIndex < len(years) and ((retValByYear[years[firstBoringYearIndex]] in (min( retValByYear.values()), max(retValByYear.values()))) or (distanceFromThisYear(years[firstBoringYearIndex], retValByYear, retYearByVal) < 5))): firstBoringYearIndex += 1 firstBoringYear = 9999 if firstBoringYearIndex < len(years): firstBoringYear = years[firstBoringYearIndex] if firstBoringYear > max(retValByYear.keys()) - 30: if verbose: print("Ran out of years to throw out.") return retValByYear if verbose: print("Throwing out %d so the rest will fit" % firstBoringYear) del retValByYear[firstBoringYear] return retValByYear
def maybeTweetTop5(city, valByYear, nextMonthYear, nextMonthMonth, monthStr, field, monthDate): yearByVal = reverseDict(valByYear) top5 = topNValuesLists(yearByVal, 5) if inListOfLists(top5.values(), monthDate.year): daysLeft = datetime.date(nextMonthYear, nextMonthMonth, 1) - datetime.date.today() tweetTopN(city, top5, monthStr, field, monthDate.year, daysLeft.days)
def annualOrderedByMonth(city, monthFilter, field, cumulative): valByYears = yearDatas(monthFilter, field, now.year, cumulative) plotDataOtherYears = [] plotDataThisYear = [] plotTics = [] plotIndex = 0 divider = 1 ylabel = field.units if max(valByYears.values()) > 10000: divider = 1000.0 ylabel = '1000s of %s' % field.units valByYears = makeFit(valByYears, 75) plotIndex = 0 last30Years = [] yearsByVal = reverseDict(valByYears) print(yearsByVal) for val in sorted(yearsByVal.keys()): yearsWithThisVal = yearsByVal[val] for plotYear in yearsWithThisVal: if now.year == plotYear: plotDataThisYear.append((plotIndex, val / divider)) else: plotDataOtherYears.append((plotIndex, val / divider)) plotTics.append('"%s" %d' % (plotYear, plotIndex)) plotIndex += 1 if (plotYear > now.year - 31) and (plotYear < now.year): last30Years.append(val / divider) avg = sum(last30Years) / len(last30Years) std = numpy.std(last30Years) #print if len(yearsByVal.keys()) > 0: plot = gnuplot.Plot('%s/svg/yearOrdering.%s.%s' % (city, monthFilter.filenamePart, field.title.replace(' ', '_').replace('℃', 'C')), yaxis=2) legend = 'on left' if max(yearsByVal.keys()) < 0: legend = 'bottom right' ymin = None if cumulative: ymin = 0 plot.open( xtics=plotTics, xticsRotate=90, xticsFont='Arial,10', legend=legend, ymin=ymin, title='%s %s for %s' % (city.capitalize(), field.title.lower(), monthFilter.chartTitle), margins=[6, 8, 2, 3], ylabel=ylabel) plot.addLine( gnuplot.Line('30-year std-dev', ((-1, avg - std), (plotIndex, avg - std), (plotIndex, avg + std), (-1, avg + std)), lineColour='#e3e3e3', plot='filledcurves')) plot.addLine( gnuplot.Line('Other years', plotDataOtherYears, plot='boxes')) plot.addLine(gnuplot.Line('This year', plotDataThisYear, plot='boxes')) plot.addLine( gnuplot.Line('30-Year Average', ((0, avg), (plotIndex - 1, avg)))) plot.plot() plot.close()
def plotdict(valueByYear, filename, chartTitle, yaxisLabel, thisYear, plotZeros=True, output='svg', ymin=None, showAverage=True): yearByValue = reverseDict(valueByYear) values = sorted(yearByValue.keys()) # chartTicks = [] chartDataOtherYears = [] chartDataThisYear = [] chartIndex = 0 maxValue = max(values) if showAverage: recentYears = tuple( filter(lambda t: t in valueByYear, range(thisYear - 30, thisYear))) recentValues = [valueByYear[t] for t in recentYears] recentAvg = sum(recentValues) / len(recentValues) recentStd = numpy.std(tuple(map(float, recentValues))) longestXTick = 0 for value in values: # number of years with this value #print 'val="%s"' % value thiscount = len(yearByValue[value]) # if plotZeros == False and value == 0: # We've been told to skip zeros, so we don't plot them continue for year in yearByValue[value]: if year == thisYear: chartDataThisYear.append((chartIndex, value, '# ' + str(year))) else: chartDataOtherYears.append( (chartIndex, value, '# ' + str(year))) xtick = str(year) longestXTick = max(longestXTick, len(xtick)) chartTicks.append('"{date}" {index}'.format(date=xtick, index=chartIndex)) chartIndex += 1 legend = 'on left' if maxValue < 0: legend = 'on right bottom' bmargin = 2 + longestXTick // 2 plot = gnuplot.Plot(filename, yaxis=2, output=output) # #, xticsFont='Arial,10' plot.open(title=chartTitle, xtics=chartTicks, xticsRotate=90, xticsFont='Arial,20', legend=legend, margins=[6, 8, 2, bmargin], ylabel=yaxisLabel, ymin=ymin, xmin=-1, xmax=chartIndex) if showAverage: plot.addLine( gnuplot.Line('30-year 2*std-dev', ((-1, recentAvg - recentStd * 2), (chartIndex, recentAvg - recentStd * 2), (chartIndex, recentAvg + recentStd * 2), (-1, recentAvg + recentStd * 2)), lineColour='#f1f1f1', plot='filledcurves')) plot.addLine( gnuplot.Line('30-year std-dev', ((-1, recentAvg - recentStd), (chartIndex, recentAvg - recentStd), (chartIndex, recentAvg + recentStd), (-1, recentAvg + recentStd)), lineColour='#e3e3e3', plot='filledcurves')) plot.addLine( gnuplot.Line('30-year average', ((-1, recentAvg), (chartIndex, recentAvg)), lineColour='0x000000')) plot.addLine( gnuplot.Line('Other years', chartDataOtherYears, plot='boxes', lineColour='0x6495ED')) plot.addLine( gnuplot.Line('This year', chartDataThisYear, plot='boxes', lineColour='0x556B2F')) plot.plot() plot.close() return plot.fname
def first(cityName, expr, name, dateRange, excludeThisYear=False, order="first", yaxisLabel=None, run=1, limitToMostRecentYears=None, verboseIfInDateRange=None, ): data = daily.load(cityName) if limitToMostRecentYears != None: ndata = daily.Data() yearsAgo = datetime.date(now.year-limitToMostRecentYears, now.month, now.day) for key in data: if key >= yearsAgo: ndata[key] = data[key] data = ndata fieldDict = { 'min': Value(daily.MIN_TEMP), 'max': Value(daily.MAX_TEMP), 'tempSpan': ValueDiff(daily.MAX_TEMP, daily.MIN_TEMP), 'rain': Value(daily.TOTAL_RAIN_MM), 'humidex': Value(daily.MAX_HUMIDEX), 'snow': Value(daily.TOTAL_SNOW_CM), 'snowpack': ValueEmptyZero(daily.SNOW_ON_GRND_CM), 'windgust': Value(daily.SPD_OF_MAX_GUST_KPH), 'wind': Value(daily.AVG_WIND), 'windchill': Value(daily.MIN_WINDCHILL), 'avgWindchill': Value(daily.MIN_WINDCHILL), } fieldValues = fieldDict.values() referencedValues=[] for fieldName in fieldDict.keys(): if '{'+fieldName+'}' in expr: referencedValues.append(fieldName) firstByYear = {} eThisYear = now.year endTimeThisYear = datetime.date(now.year, dateRange.endMonth, dateRange.endDay) if dateRange.yearCross() and now < endTimeThisYear: eThisYear -= 1 for baseyear in range(data.minYear, data.maxYear-dateRange.todayIsYearCross()+1): try: dayOffset = datacache.readCache(cityName, baseyear, '{}.{}.{}.{}'.format(name,order,str(dateRange),expr)) if dayOffset != None: firstByYear[baseyear] = dayOffset continue except datacache.NotInCache: pass starttime = datetime.date(baseyear, dateRange.startMonth, dateRange.startDay) endtime = datetime.date(baseyear+dateRange.yearCross(), dateRange.endMonth, dateRange.endDay) #print baseyear, starttime, endtime dayRange = daily.dayRange(starttime,endtime) if order=="last": dayRange = daily.dayRange(endtime-datetime.timedelta(1), starttime-datetime.timedelta(1), -1) if excludeThisYear and baseyear == now.year-dateRange.yearCross(): # don't consider this year when looking for records for last event, because this year is not over continue expectedDayCount = 0 observedDayCount = 0 for day in dayRange: expectedDayCount += 1 if day in data: observedDayCount += 1 else: continue if baseyear in firstByYear: # We already figured out which day was first continue vals = {} for fieldName, fieldCall in fieldDict.items(): try: vals[fieldName] = fieldCall(data[day], day) vals[fieldName+'Flag'] = '"' + fieldCall.getFlag(data[day]) + '"' except KeyError: #print day, 'KeyError' vals[fieldName] = None skip=False usedVals={} for fieldName in fieldDict.keys(): if fieldName in referencedValues: if vals[fieldName] is None: #print 'Skipping {} because {} is None'.format(day, fieldName) skip=True break usedVals[fieldName] = vals[fieldName] if skip: continue #resolvedExpr = expr.format(**vals) #print(vals) #val = eval(expr) val = eval(expr, vals) #if True: #day == datetime.date(2015,10,17): #print day, resolvedExpr, val if val is True: dayOffset = (day - starttime).days firstByYear[baseyear] = dayOffset break observedPercent = observedDayCount * 100 / expectedDayCount if observedPercent < 85 and baseyear != eThisYear: print('Skipping {baseyear} because it only had {observedPercent:.1f}% of the days'.format(**locals())) if baseyear in firstByYear: firstByYear.pop(baseyear) elif baseyear not in firstByYear and baseyear != eThisYear: print('Event did not happen during {}.'.format(baseyear)) datacache.cacheThis(cityName, baseyear, '{}.{}.{}.{}'.format(name,order,str(dateRange),expr), firstByYear.get(baseyear, None)) yearByFirst = reverseDict(firstByYear) #for offset in sorted(yearByFirst.keys()): # for year in yearByFirst[offset]: # print datetime.date(year, dateRange.startMonth, dateRange.startDay) + datetime.timedelta(offset) #print yearByFirst verbose = False if verboseIfInDateRange == None: verbose = True elif eThisYear in firstByYear: thisYearFirstDayOffset = firstByYear[eThisYear] firstThisYearDate = ( datetime.date(eThisYear, dateRange.startMonth, dateRange.startDay) + datetime.timedelta(thisYearFirstDayOffset) ) if ( firstThisYearDate >= verboseIfInDateRange[0] and firstThisYearDate <= verboseIfInDateRange[1] ): verbose = True (earliest, secondEarliest, *ignore) = sorted(filter(lambda y: y!=now.year-dateRange.yearCross(), yearByFirst.keys()))[:2] + [None,None] (secondLatest, latest, *ignore) = sorted(filter(lambda y: y!=now.year-dateRange.yearCross(), yearByFirst.keys()))[-2:] + [None,None] earliestYears = lookupEmptyListIfNone(yearByFirst, earliest) secondEarliestYears = lookupEmptyListIfNone(yearByFirst, secondEarliest) latestYears = lookupEmptyListIfNone(yearByFirst, latest) secondLatestYears = lookupEmptyListIfNone(yearByFirst, secondLatest) earliestDates=[] latestDates=[] if verbose: earliestDates=showRecords(name, 'earliest', earliestYears, firstByYear, dateRange) showRecords(name, '2nd earliest', secondEarliestYears, firstByYear, dateRange) showRecords(name, '2nd latest', secondLatestYears, firstByYear, dateRange) latestDates=showRecords(name, 'latest', latestYears, firstByYear, dateRange) avgKeys = filter(lambda t: t in firstByYear, range(eThisYear-30, eThisYear)) offsets = sorted([firstByYear[a] for a in avgKeys]) avg = calcAvg(offsets) median = calcMedian(offsets) avgFirst = calcDate(eThisYear, dateRange.startMonth, dateRange.startDay, avg) medianFirst = calcDate(eThisYear, dateRange.startMonth, dateRange.startDay, median) if verbose: print("average {name} is {avgFirst}".format(**locals())) print("median {name} is {medianFirst}".format(**locals())) ret = None if eThisYear in firstByYear: thisYearFirst = firstByYear[eThisYear] countEarlier = 0 countEqual = 0 countLater = 0 recentCountEarlier = [] recentCountEqual = [] recentCountLater = [] for first in sorted(yearByFirst.keys()): if first < thisYearFirst: countEarlier += len(yearByFirst[first]) recentCountEarlier += filter(lambda y: y<eThisYear and y > eThisYear-31, yearByFirst[first]) elif first == thisYearFirst: countEqual += len(yearByFirst[first]) - 1 #Subtract the current year recentCountEqual += filter(lambda y: y<eThisYear and y > eThisYear-31, yearByFirst[first]) else: countLater += len(yearByFirst[first]) recentCountLater += filter(lambda y: y<eThisYear and y > eThisYear-31, yearByFirst[first]) totalCount = countEarlier + countEqual + countLater totalRecentCount = len(recentCountEarlier) + len(recentCountEqual) + len(recentCountLater) firstThisYear = ( datetime.date(eThisYear, dateRange.startMonth, dateRange.startDay) + datetime.timedelta(thisYearFirst) ) if verbose: print('%s %s was %s' % (now.year, name, firstThisYear )) print('%d%% of last %d years were earlier.' % (round(countEarlier * 100.0 / totalCount), totalCount)) print('%d%% of last %d years were the same.' % (round(countEqual * 100.0 / totalCount), totalCount)) print('%d%% of last %d years were later.' % (round(countLater * 100.0 / totalCount), totalCount)) print('%d of last %d years were earlier.' % (len(recentCountEarlier), totalRecentCount), sorted(recentCountEarlier)) print('%d of last %d years were the same.' % (len(recentCountEqual), totalRecentCount), sorted(recentCountEqual)) print('%d of last %d years were later.' % (len(recentCountLater), totalRecentCount), sorted(recentCountLater)) ret = firstThisYear, medianFirst, earliestDates, latestDates else: print('Not showing this year because eThisYear="{}" and firstByYear="{}"'.format(eThisYear, firstByYear)) if len(yearByFirst) == 0: # There's nothing to plot, so don't even bother trying return ret plotDataOtherYears = [] plotDataThisYear = [] dateLabels = [] for key in sorted(yearByFirst.keys()): if eThisYear in yearByFirst[key]: plotDataThisYear.append((key, len(yearByFirst[key]), '#%s' % ','.join(map(str, yearByFirst[key])) )) else: plotDataOtherYears.append((key, len(yearByFirst[key]), '#%s' % ','.join(map(str, yearByFirst[key])))) histogramFname = '%s/svg/%s' % (cityName, name.replace(' ', '_')) if ret != None: ret = ret + (histogramFname,) plot = gnuplot.Plot(histogramFname) dateMin = min(yearByFirst.keys()) dateMax = max(yearByFirst.keys()) plotDateMin = dateMin-1 #int(dateMin - (dateMax - dateMin)*.25) plotDateMax = dateMax+1 #int(dateMax + (dateMax - dateMin)*.25) for dayOffset in range(plotDateMin, plotDateMax+1, 1): date = datetime.date(now.year, dateRange.startMonth, dateRange.startDay) + datetime.timedelta(dayOffset) if date.day % 5 == 1 and date.day != 31: dateLabels.append('"%s" %d' % (date.strftime('%b/%d'), dayOffset)) ylabel = 'Number of years when %s happened on this day' % name if yaxisLabel != None: ylabel = yaxisLabel plot.open(title='%s in %s' % (name, cityName.capitalize()), ylabel=ylabel, xmin=plotDateMin, xmax=plotDateMax, ymin=0, ymax=max(len(a) for a in yearByFirst.values())+1, xtics=dateLabels, xticsRotate=45, margins=[7,8,2,5]) plot.addLine(gnuplot.Line('Other years', plotDataOtherYears, plot='boxes')) plot.addLine(gnuplot.Line('This year', plotDataThisYear, plot='boxes')) plot.plot() plot.close() print('') plotdata = [] for (year, plotCount) in firstByYear.items(): if plotCount != None: plotdata.append( (year, plotCount, ( '#' +str(datetime.date(year, dateRange.startMonth, dateRange.startDay) + datetime.timedelta(plotCount))))) ytics = [] for m in range(1,13): for d in (1,10,20): thisDate = datetime.date(2015,m,d) dayOffset = (thisDate - datetime.date(2015, dateRange.startMonth, dateRange.startDay)).days ytics.append('"%s" %d' % (thisDate.strftime('%b %d'), dayOffset)) #print(tuple(t[:2] for t in plotdata)) lineFit = linear.linearTrend(tuple(t[:2] for t in plotdata)) plot = gnuplot.Plot("%s/svg/%s_%s-%u_%s.line" % (cityName, name.replace(' ', '_'), monthName(dateRange.startMonth), dateRange.startDay, dateRange.lastDay().replace(' ', '-') ), yaxis=2) plot.open(title='%s %s between %s %u and %s' % (cityName.capitalize(), name, monthName(dateRange.startMonth), dateRange.startDay, dateRange.lastDay() ), ytics=ytics) plot.addLine(gnuplot.Line("Linear", lineFit, lineColour='green', plot='lines')) plot.addLine(gnuplot.Line("Date", plotdata, lineColour='purple')) plot.plot() plot.close() return ret
def main( city, force=False, lastCheckedValue=None, today=None, maxValueToCheck=None, allYear=False, allWinter=False, justTop5=False, dataSinceDay=None, ): #import pdb; pdb.set_trace() data = daily.load(city) if dataSinceDay is not None: for day in tuple(data.keys()): if day < dataSinceDay: del data[day] monthlyAverages.cityData = data if today is None: today = daily.timeByCity[city].date() #if allYear: # today = dt.date(daily.timeByCity[city].date().year, 12, 31) yearToCheck = (today - dt.timedelta(7)).year if yearToCheck != today.year: today = dt.date(yearToCheck, 12, 31) monthlyAverages.now = today tomorrow = today + dt.timedelta(days=1) todayMaxInfo = dailyRecords.getInfo(city, today, daily.MAX_TEMP) todayAverageMax = roundAwayFromZero(todayMaxInfo.normal) todayMax = None if todayMaxInfo.recent is not None: todayMax = int(todayMaxInfo.recent) if lastCheckedValue is None: minValueToCheck = int(todayAverageMax) + 2 else: minValueToCheck = int(lastCheckedValue) + 1 if maxValueToCheck is None: maxValueToCheck = todayMax if todayMax is None: maxValueToCheck = 35 maxValuesToCheck = filter(lambda t: t % 10 == 0, range(minValueToCheck, maxValueToCheck + 1)) fieldList = [ #*[ ( ExprVal('max>={} if max is not None else None'.format(t), # title=str(t) + "℃", # name="max>=" + str(t), # units="days", # unit="day", # precision=0, # field=daily.MAX_TEMP, # description=str(t)+"℃ days"), # True ) for t in maxValuesToCheck ], #[ ( ExprVal('maxHumidex>max and maxHumidex>=' + str(t), # title=str(t) + " humidex", # name="humidex>=" + str(t), # units="days", # unit="day", # precision=0), # True ) for t in range(30, 51) ] #( FractionVal(daily.TOTAL_RAIN_MM, "Rain"), True ), (FractionVal(daily.TOTAL_SNOW_CM, "snow"), True), #( FractionVal(daily.TOTAL_PRECIP_MM, "precipitation"), True ), #( FractionVal(daily.AVG_WIND, "Wind"), False ), #( Avg(daily.MIN_TEMP, daily.MAX_TEMP, "temperature"), False ), #( FractionVal(daily.MEAN_HUMIDITY, "Humidity"), False ), ] monFilter = monthlyAverages.BeforeDateFilter(month=tomorrow.month, day=tomorrow.day) if allYear: monFilter = monthlyAverages.BeforeDateFilter(month=1, day=1) if allWinter: monFilter = WinterFilter() todayFilter = monthlyAverages.OneDayFilter(month=today.month, day=today.day) #import pudb; pu.db for field, cumulative in fieldList: thisyear = today.year if allWinter: thisyear = today.year if today.month >= 7 else today.year - 1 valByYear = monthlyAverages.yearDatas(monFilter, field=field, lastYear=thisyear, cumulative=cumulative) normalVal = monthlyAverages.normalMonthlyDatas(today.year, monFilter, field, cumulative=cumulative) if thisyear not in valByYear: continue ci80 = confidenceInterval80(valByYear, thisyear) ci50 = confidenceInterval50(valByYear, thisyear) thisYearVal = valByYear[thisyear] maxSince = None minSince = None for year in reversed(sorted(valByYear.keys())): if year != thisyear: val = valByYear[year] if maxSince is None and val >= thisYearVal: maxSince = year if minSince is None and val <= thisYearVal: minSince = year del val insideCi80 = (thisYearVal >= ci80.low and thisYearVal <= ci80.high) insideCi50 = (thisYearVal >= ci50.low and thisYearVal <= ci50.high) if field.units == 'days': # and False: todayValByYear = monthlyAverages.yearDatas(todayFilter, field=field, lastYear=today.year, cumulative=cumulative) if todayValByYear[today.year] == 0: #The countable event did not occur today, so skip. continue #import pdb; pdb.set_trace() yearByVal = reverseDict(valByYear) top5 = topNValuesLists(yearByVal, 5) if ((allYear or allWinter) and (inListOfLists(top5.values(), thisyear) or args.force)): tweetTopN(city, top5, field, thisyear) if justTop5: continue amountDescription = '' if thisYearVal < ci50[0] and (field.units != 'days' or thisYearVal != 0): amountDescription = 'just ' amount = formatWithUnits(thisYearVal, field, field.precision) if field.units == 'days': amount = formatWithUnits(thisYearVal, field, 0, skipUnits=True) if thisYearVal == 0: amount = "no" aboveBelow = 'above' if thisYearVal < normalVal.average: aboveBelow = 'below' if cumulative: deviation = deviationDescription(thisYearVal, normalVal.average, field) else: diff = abs(thisYearVal - normalVal.average) deviation = formatWithUnits(diff, field, field.precision) + ' ' + aboveBelow start = 'average' if cumulative: start = 'total' title = field.title.lower() ci80Low = formatWithUnits(ci80[0], field, field.precision, skipUnits=True) ci80High = formatWithUnits(ci80[1], field, field.precision) insideOutside = 'outside' if insideCi80: insideOutside = 'inside' cityName = stations.city[city].name avgWithUnits = formatWithUnits(normalVal.average, field, field.precision) #tweetText = ( # '#{cityName}\'s {start} {title} so far this year was' # ' {amountDescription}{amount},' # ' {deviation} {aboveBelow} average; {insideOutside} the normal range of' # ' {ci80Low} to {ci80High}'.format(**locals())) tweetText = ('#{cityName}\'s {start} {title} so far this year was' ' {amountDescription}{amount},' ' {deviation} the average of' ' {avgWithUnits}'.format(**locals())) if allYear: tweetText = ( '#{cityName}\'s {start} {title} during {today.year} was' ' {amountDescription}{amount},' ' {deviation} the average of' ' {avgWithUnits}'.format(**locals())) if field.units == 'days': units = field.unit if thisYearVal != 1: units = field.units nth = alert.nth(thisYearVal) #import pdb; pdb.set_trace() average = formatWithUnits(normalVal.average, field, precision=1) todayString = 'Today is' if today != daily.timeByCity[city].date(): todayString = 'Yesterday was' tweetText = ( '{todayString} #{cityName}\'s {nth} {title} day so far this year,' ' {deviation} the average of {average}.'.format(**locals())) #alertTweets.maybeTweetWithSuffix(city, tweetText) recordMin = normalVal.minimum recordMax = normalVal.maximum print( 'Record minimum was {recordMin}{field.units} in {normalVal.minYear}' .format(**locals())) print( 'Record maximum was {recordMax}{field.units} in {normalVal.maxYear}' .format(**locals())) plotIt = not insideCi80 or args.force if maxSince is None: print('***Max since records began in {}.'.format( min(valByYear.keys()))) plotIt = True elif today.year - maxSince > 10: maxSinceVal = valByYear[maxSince] print('***Max since {maxSince}:{maxSinceVal}{field.units}'.format( **locals())) plotIt = True if minSince is None: print('***Min since records began in {}.'.format( min(valByYear.keys()))) plotIt = True elif today.year - minSince > 10: minSinceVal = valByYear[minSince] print('***Min since {minSince}:{minSinceVal}{field.units}'.format( **locals())) plotIt = True if plotIt: fname = field.name pngname = "%s/Annual_%s.png" % (city, fname) alertTweets.maybeTweetWithSuffix(city, tweetText, fname=pngname) if todayMax is None: return todayAverageMax + 2 return max(todayMax, todayAverageMax + 2)