KeyError: 20, not sure what is wrong - python

I am new to python and am currently trying to create a program that will create a list of yearly percentage changes in revenue.
This is what I have so far:
revs = {}
for year in range(14, 20):
revs[year] = float(input("Revenue in {0}: ".format(year)))
revs_change = []
for year in range(14, 20):
next_year = year + 1
revs_change.append((revs[next_year] - revs[year])/revs[year])
print(revs_change[0])
The error comes on the 8th line and it has something to do with using the variable next_year.
Thanks!

If you are going to print out the values of year and next_year the problem is that there is no value for revs[next_year=20].
One way is to do like this-
start = 14
end = 21
for year in range(start, end):
revs[year] = float(input("Revenue in {0}: ".format(year)))
revs_change = []
for year in range(start, end-1):
next_year = year + 1
print(f"Year: {year}, Next Year: {next_year}\n")
revs_change.append( (revs[next_year] - revs[year])/revs[year] )
print(revs_change[0])

In Python, the end limit for any range you give will be taken excluding that upper limit i.e if you set the upper limit as 20, it evaluates up to 19. Give the upper limit as 21 so that you'll get the desired output

Related

Pandas raising ValueError when generating Timestamps using for loops

When generating a few random Timestamps using Pandas Timestamp() method I'm running into this ValueError: day is out of range for month. It seems totally illogical and this error is thrown only when using for loops (conventional for loops & list comprehensions). Works fine with while loops. I would like to know why this happens since the logic behind what I'm trying to do is trivial and I don't see any reason for this to happen? I'd appreciate any help regarding this. Here are the code segments:
idx = list()
for day in range(10):
idx.append(pd.Timestamp(year = 1997, month = 1, day = day, hour = 12))
pd.Series(list(ascii_uppercase), index = idx)
[pd.Timestamp(year = 1997, month = 5, day = i, hour = 14, minute = i + 24, microsecond = i + 49) for i in range(27)]
The above two raise ValueError.
day = 1
dates = list()
while (day < 31):
print(pd.Timestamp(year = 2018, month = 6, day = day, hour = 13, minute = 10 + day, second = 26 + day))
dates.append(pd.Timestamp(year = 2018, month = 6, day = day, hour = 13, minute = 10 + day, second = 26 + day))
day += 1
While the above works fine!.
P.S: The ranges I specified for days did never exceed the total days in the specified month. You can see that yourself in the code.
Oopsies. I figured the issue!
range(n)
will generate integers starting from 0. Which isn't a valid date! That's why I had that error. That's pretty damn stupid of me. Once I changed
range(1, n)
It worked fine!

Iteration through a list

I'm very new to Python hence this question.
I have a list that represents dates i.e. Mondays in March and beginning of April
[2, 9, 16, 23, 30, 6]
The list, 'color_sack' is created from a scrape of our local council website.
Im using
next_rubbish_day = next(x for x in color_sack if x > todays_date.day)
todays_date.day returns just the number representing the day i.e. 30
This has worked well all month until today 30th when it now displays a error
next_rubbish_day = next(x for x in color_sack if x > todays_date.day)
StopIteration
Is it possible to step through the list a better way so as next_rubbish_day would populate the 6 after the 30 from the list above.
I can see why its not working but can't work out a better way.
When April starts the list will be updated with the new dates for Mondays in April through to the beginning of May
Consider, if your current month is march and corresponding list of dates is [2, 9, 16, 23, 30, 6] and today's date is 30, basically what we are doing is :
Checking if there is any date in color_sack that is greater than
today's date if it is then we yield that date. In our case no date in the list is greater than 30.
If the 1st condition fails we now find out the index of maximum date in the color_sack, in our case the max date is 30 and its index is 4, now we found out if there is a idx greater than the index of maximum date in the list, if it is then we return that date.
This algorithm will comply with any dates in the current month eg March. As soon as the new month starts eg. "April starts the list will be updated with the new dates for Mondays in April through to the beginning of May".
So this algorithm will always comply.
Try this:
def next_rubbish_day(color_sack, todays_date):
for idx, day in enumerate(color_sack):
if day > todays_date or idx > color_sack.index(max(color_sack)):
yield day
print(next(next_rubbish_day(color_sack, 6)))
print(next(next_rubbish_day(color_sack, 10)))
print(next(next_rubbish_day(color_sack, 21)))
print(next(next_rubbish_day(color_sack, 30)))
print(next(next_rubbish_day(color_sack, 31)))
OUTPUT:
9
16
23
6
6
next takes an optional default that is returned when the iterable is empty. If color_sack consistently has the first-of-next-month day in the last position, return it as a default:
next_rubbish_day = next(
(x for x in color_sack[:-1] if x > todays_date.day),
color_sack[-1],
)
Note that this scheme will not tell you whether you rolled over. It will only tell you the next date is 6th, not 6th of April versus 6th of March.
To avoid the magic indices, consider splitting your list explicitly and giving proper names to each part.
*this_month, fallback_day = color_sack
next_rubbish_day = next(
(day for day in this_month if day > todays_date.day),
fallback_day,
)
If you need to be month-aware, handle the StopIteration explicitly:
try:
day = next(x for x in color_sack[:-1] if x > todays_date.day)
except StopIteration:
day = color_sack[-1]
month = 'next'
else:
month = 'this'
print(f'Next date is {day} of {month} month')
Thank you for the help, Ive used MisterMiyagi snippet as that seems to work at the moment.
Here is the full code:
import datetime
import requests
import calendar
from bs4 import BeautifulSoup
from datetime import date
def ord(n): # returns st, nd, rd and th
return str(n) + (
"th" if 4 <= n % 100 <= 20 else {
1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th")
)
# Scrapes rubbish collection dates
URL = "https://apps.castlepoint.gov.uk/cpapps/index.cfm?roadID=2767&fa=wastecalendar.displayDetails"
raw_html = requests.get(URL)
data = BeautifulSoup(raw_html.text, "html.parser")
pink = data.find_all('td', class_='pink', limit=3)
black = data.find_all('td', class_='normal', limit=3)
month = data.find('div', class_='calMonthCurrent')
# converts .text and strip [] to get month name
month = str((month.text).strip('[]'))
todays_date = datetime.date.today()
print()
# creats sack lists
pink_sack = []
for div in pink:
n = div.text
pink_sack.append(n)
pink_sack = list(map(int, pink_sack))
print(f"Pink list {pink_sack}")
black_sack = []
for div in black:
n = div.text
black_sack.append(n)
black_sack = list(map(int, black_sack))
print(f"Black list {black_sack}")
# creats pink/black list
color_sack = []
color_sack = [None]*(len(pink_sack)+len(black_sack))
color_sack[::2] = pink_sack
color_sack[1::2] = black_sack
print(f"Combined list {color_sack}")
print()
print()
# checks today for rubbish
if todays_date.day in color_sack:
print(f"Today {(ord(todays_date.day))}", end=" ")
if todays_date.day in pink_sack:
print("is pink")
elif todays_date.day in black_sack:
print("is black")
# Looks for the next rubbish day
next_rubbish_day = next(
(x for x in color_sack[:-1] if x > todays_date.day),
color_sack[-1],
)
# gets day number
day = calendar.weekday(
(todays_date.year), (todays_date.month), (next_rubbish_day))
# print(next_rubbish_day)
print(f"Next rubbish day is {(calendar.day_name[day])} the {(ord(next_rubbish_day))}" +
(" and is Pink" if next_rubbish_day in pink_sack else " and is Black"))
print()
Theres probable so many more efficient ways of doing this, so Im open to suggestions and always learning.

Why Is This Day Counter Producing The Wrong Results?

Hi I'm a beginner at Python and am currently using Python 3.4.1 on PyCharm. I have recently made a project that calculates the amount of days between 2 dates, but there are 2 problems.
def get_first_day():
while True:
try:
print('First Date')
day = int(input('Day:'))
month = int(input('Month:'))
year = int(input('Year:'))
print(day, '/', month, '/', year)
date = [day, month, year * 365]
get_second_day(date)
except ValueError:
print('You were supposed to enter a date.')
def get_second_day(date_1):
while True:
try:
print('Second Date')
day = int(input('Day:'))
month = int(input('Month:'))
year = int(input('Year:'))
print(day, '/', month, '/', year)
date = [day, month, year * 365]
convert_dates_and_months(date_1, date)
except ValueError:
print('You were supposed to enter a date.')
def convert_dates_and_months(date_1, date_2):
days_unfiltered = [date_1[0], date_2[0]]
months_unfiltered = [date_1[1], date_2[1]]
year = [date_1[2], date_2[2]]
date_unfiltered = zip(days_unfiltered, months_unfiltered, year)
for d, m, y in date_unfiltered:
if m in [1, 3, 5, 7, 8, 10, 12]:
a = 31
elif m in [4, 6, 9, 11]:
a = 30
elif m in [2, 0] and int(y) % 4 is 0:
a = 29
else:
a = 28
m *= a
days = list(filter(lambda x: 0 < x < (a + 1), days_unfiltered))
months = list(filter(lambda x: 0 < x < 13, months_unfiltered))
date_1 = [days[0], months[0], year[0]]
date_2 = [days[1], months[1], year[1]]
determine_date_displacement(date_1, date_2)
def determine_date_displacement(date_1, date_2):
full_dates = zip(date_1, date_2)
days = -1
for k, v in full_dates:
days += (int(v) - int(k))
if days < 0:
days *= -1
print(days)
get_first_day()
The first problem is that the counter returns an incorrect number of days between 2 dates. The second is that def get_second_day repeats at the end for some reason. I'll show you what I mean:
First Date
Day:10
Month:09
Year:03
10 / 9 / 3
Second Date
Day:06
Month:06
Year:06
6 / 6 / 6
1087
Second Date
Day:
I know for a fact there are exactly 1,000 days between 10/09/03 and 06/06/06, yet the project returns 1,087 days.
If anyone could explain why this project is returning an incorrect number, as well as why it asks me to fill the second date again at the end, that would be perfect.
As this is my first question and I'm a beginner at Python, I apologise in advance for any weird phrasing/bad practices seen in this question.
Problem 1:
Your leap year calculation is off:
Leap years are years % 4 == 0 but only for years not year % 100 == 0 unless
they are also year % 400 == 0:
2004,2008,2012 : leap year (%4==0, not %100==0)
1700,1800,1900 : no leap year (%4 == 0 , % 100 == 0 but not %400 == 0)
1200,1600,2000 : leap years (* 1200 theor. b/c gregorian cal start)
Problem 2:
In your input you premultiply the year by 365 w/o checking for leap-years - they schould have 366 days but got 365 - which would result in lacking days when computing the amount of days for years that leap(ed).
Problem 3:
You have a controlflow-issue: the get_second_day() repeats because you do:
get_first_date()
while without end:
do smth
call get_second_date(..)
while without end:
do smth
call some calculation functions
that calc and print and return with None
back in get_second_date(), no break, so back to the beginning
of its while and start over forever - you are TRAPPED
fix it by putting a break after convert_dates_and_months(date_1, date) inside get_second_day(..)
Suggestions:
You can streamline the input by reducing the amount of duplicate code between get_first_day() and get_second_day() - this follows the DRY principle (Don't Repeat Yourself):
def getDate(text):
while True:
try:
print(text)
day = int(input('Day:'))
month = int(input('Month:'))
year = int(input('Year:'))
print(day, '/', month, '/', year)
return [day, month, year * 365] # see Problem 2
except ValueError:
print('You were supposed to enter a date.')
def get_first_day():
date1 = getDate("First Date")
# rest of code omitted
def get_second_day(date_1):
date = getDate("Second Date")
# rest of code omitted
A better solution would utilize datetime and datettime-parsing, especially if you want to handle input validation and leap-year estimation you would need far more checks.
Using datetime module would simplyfy this a lot:
import datetime
def getDate(text):
while True:
try:
print(text)
day = int(input('Day:'))
month = int(input('Month:'))
year = int(input('Year (4 digits):'))
print(day, '/', month, '/', year)
# this will throw error on invalid dates:
# f.e. 66.22.2871 or even (29.2.1977) and user
# gets a new chance to input something valid
return datetime.datetime.strptime("{}.{}.{}".format(year,month,day),"%Y.%m.%d")
except (ValueError,EOFError):
print('You were supposed to enter a valid date.')
def get_first_day():
return getDate("First Date")
def get_second_day():
return getDate("Second Date")
# while True: # uncomment and indent next lines to loop endlessly
first = get_first_day() # getDate("First Date") and getDate("Second Date")
second = get_second_day() # directly would be fine IMHO, no function needed
print( (second-first).days)
Output:
First Date
Day:10
Month:9
Year (4 digits):2003
10 / 9 / 2003
Second Date
Day:6
Month:6
Year (4 digits):2006
6 / 6 / 2006
1000
Good read: How to debug small programs (#1) - following it, could have at least lead you to the control-flow issue.

Holiday program error in Python

I was asked to make a program that takes the day that I start my travel length of my stay as an input and give the day that I return in as an output so I tried this and I keep getting an error that says (KeyError = -3)
Note: I'm a beginner, so be easy on me :)
dect = {0:'Sunday', 1: 'Monday', 2:'Tuesday',3:'Wednesday', 4:'Thursday',
5:'Friday',6:'Saturday'
}
def day(x):
print( dect[x])
def holiday(start,length):
length = length + start
while True:
if length <= 0:
break
print(length)
else:
length = length - 7
day(length)
s = int(input('Enter the start day: '))
l = int(input('Enter the length of your stay: '))
holiday(s,l)
The value of length can become negative and this is why you're looking up -3 in the dictionary.
For example, if my holiday is 4 days (length = 4) then you're doing length = length - 7 which means you're calling day() with -3 as value.
Your dictionary only has values for the keys 0 - 6 and that's why you're getting a KeyError as the key -3 is not in the dictionary.
You can fix it by changing the check into if length < 7.
What you can do is, simply use datetime module.
from datetime import date,timedelta
d = date.today() + timedelta(days=2)
print d
You can use timedelta to add as many days/hours/whatever you want. See here for the documentation.
Replace your holiday function as -
def holiday(start,length):
length = length + start
length = length % 7
day(length)

Comparing two lists to find the average

The list should hold the average profit of all januaries and all februaries etc;
The way I thought you could do this was to compare to lists, such as year 1 list would have value for janurary, feb, march,...,december and from there I could find the average profit based on the years, This hasn't been working and I'm not sure where to go from here. Any suggestions?
MONTHS = 12
def average_profit(years):
assert years >= 0, "Years cannot be negative"
total = 0.0
monthly_average = 0.0
total_months = years * MONTHS
total_list = []
average_list=[]
percentage_list = []
for i in range(0, years):
yearly_total = 0.0
for j in range(1, 13):
monthly_profit = float(input("Please enter the profit made in month {0}: ".format(j)).strip())
monthly_average = monthly_average + monthly_profit
month_average = monthly_average/j
total_list.append(monthly_profit)
average_list.append(month_average)
yearly_total = yearly_total + monthly_profit
total_percent = (monthly_profit/12)*100
percentage_list.append(total_percent)
print("Total this year was ${0:.2f}".format(yearly_total))
total = total + yearly_total
average_per_month = total / total_months
return total, average_per_month, total_months, total_list, average_list, percentage_list
Your problem is most likely that for i in range(0, years) should be changed to for i in range(0, years). You do this right with the months, but getting it right with the years is just as important.
It seems to me that a better data structure could help with this a good bit. It's hard to tell what your best bet will be, but one suggestion could be to use a dict (defaultdict is even easier):
from collections import defaultdict:
d = defaultdict(list)
for i in range(0,years):
for month in range(1,13)
d[month].append(float(input()))
#now find the sum for each month:
for i in range(1,13):
print sum(d[i])/len(d[i])
Of course, we could use a list instead of a dictionary, but the dictionary would allow you to use month names instead of numbers (which may be kind of nice -- and I bet you could get their names pretty easily from the calendar module.)

Categories