Как кто-то предложил, я подготовил проверенный пример. Если вы выберете из него панды и просто поместите необработанные значения вместо значений dataframe, он отлично работает.
Если вы вернете в него панд, как я уже сказал ниже, программа запустится и вернет 0 для печати (true_age).
import pandas as pd
import numpy as np
from datetime import datetime
data = np.array([['','bornYear','bornMonth', 'bornDay','diedYear','diedMonth','diedDay'],
['Record1',1932,8,17,1980,3,22],
['Record2',1950,4,12,1980,3,22]])
df = pd.DataFrame(data=data[1:,1:],
index=data[1:,0],
columns=data[0,1:])
byear = int(df.iloc[1]['bornYear'])
bmonth = int(df.iloc[1]['bornMonth'])
bday = int(df.iloc[1]['bornDay'])
died_year = df.iloc[1]['diedYear']
died_month = df.iloc[1]['diedMonth']
died_day = df.iloc[1]['diedDay']
now_year = datetime.now().year
now_month = datetime.now().month
now_day = datetime.now().day
age_raw = now_year - byear
true_age = 0
if died_year is not None:
died_year = int(died_year)
died_month = int(died_month)
died_day = int(died_day)
age_raw = float(died_year) - float(byear)
if bmonth > died_month:
if bday > died_day:
true_age = age_raw - 1
elif bday < died_day:
true_age = age_raw
elif bmonth < died_month:
true_age = age_raw
print(true_age)
Итак, у меня есть фрейм данных pandas, который является результатом запроса MySQL, который ищет имя пользователя и затем возвращает некоторую информацию о них. Одной из таких сведений является их возраст. В таблице представлены как живые, так и умершие люди. Я пытаюсь сделать так, чтобы, если человек умер, он использует свой фактический возраст (в момент смерти), а не то, что их возраст будет, если они еще живы. Если они все еще живы, поля для даты смерти пусты; если они мертвы, эти области, конечно, имеют ценности. Вот соответствующие переменные, которые я объявил:
bmonth = int(storage.iloc[0]['birthMonth'])
bday = int(storage.iloc[0]['birthDay'])
byear = int(storage.iloc[0]['birthYear'])
died_year = storage.iloc[0]['deathYear']
died_month = storage.iloc[0]['deathMonth']
died_day = storage.iloc[0]['deathDay']
now_year = datetime.now().year
now_month = datetime.now().month
now_day = datetime.now().day
age_raw = now_year - byear
true_age = 0
Теперь у меня это разработано как вложенные операторы if, но я где-то ошибся. Если человек жив, все работает правильно; когда я печатаю возраст, он выводит правильный возраст. Если человек умер, однако, печатный возраст всегда равен нулю. Вот вложенные операторы if, а также соответствующий оператор печати:
#Here are the nested if statements:
if died_year is None:
if bmonth > now_month:
if bday > now_day:
true_age = age_raw - 1
elif bday < now_day:
true_age = age_raw
elif bmonth < now_month:
true_age = age_raw
elif died_year is not None:
died_year = int(died_year)
died_month = int(died_month)
died_day = int(died_day)
age_raw = died_year - byear
if bmonth > died_month:
if bday > died_day:
true_age = age_raw - 1
elif bday < died_day:
true_age = age_raw
elif bmonth < died_month:
true_age = age_raw
#And now the print statement:
print("DOB: "+str(bmonth)+"/"+str(bday)+"/"+str(byear)+" ("+str(true_age)+" years old)")
Кроме того, у меня есть следующее место, так что дата смерти возвращается на выходе, если человек умер. Он работает нормально и возвращает правильную дату, поэтому я знаю, что все значения верны:
if died_year is not None:
print("*DECEASED: "+str(died_month)+"/"+str(died_day)+"/"+str(died_year))
Обратите внимание, что я не преобразовал переменные die_year, died_month и die_day в целые числа до тех пор, пока не будут выполнены соответствующие условия; выполнение этого вне оператора if вызвало бы ошибку, поскольку нулевые значения не могут быть переданы как int(). Я чувствую, что мне не хватает чего-то сверх очевидного здесь, но, возможно, нет. Кроме того, если у кого-то есть лучший способ сделать все это, я всегда буду учиться тому, как быть более эффективным.
Pandas имеет фантастическую поддержку временных рядов, поэтому неплохо использовать соответствующие инструменты. После преобразования наших столбцов в один столбец Datetime мы можем выполнить арифметику времени на нем:
# demo dataframe
df = pd.DataFrame({
'birthMonth': [5, 2],
'birthDay': [4, 24],
'birthYear': [1924, 1997],
'deathMonth': [3, None],
'deathDay': [1, None],
'deathYear': [2008, None]
})
# convert birth dates to datetimes
birth = pd.to_datetime(df[['birthMonth', 'birthDay', 'birthYear']]
.rename(columns={'birthMonth': 'month', 'birthDay': 'day', 'birthYear': 'year'}))
# convert death dates to datetimes
death = pd.to_datetime(df[['deathMonth', 'deathDay', 'deathYear']]
.rename(columns={'deathMonth':'month', 'deathDay': 'day', 'deathYear': 'year'}))
# calculate age in days, normalizing 'now' to midnight of today
age = (pd.Timestamp.now().normalize() - birth).where(death.isnull(), other=death-birth)
Изменить: см. Обсуждение @ALollz ниже относительно нормализации метрики.
pd.Timestamp.now().normalize()
таким образом, чтобы живые люди имели часть времени 00: 00; 00, поскольку такой информации для умершего нет.
Вы можете определить функцию, которая вычисляет возраст человека:
from datetime import date
def calc_age(row):
bm = row['bornMonth']
bd = row['bornDay']
by = row['bornYear']
dm = row['diedMonth']
dd = row['diedDay']
dy = row['diedYear']
birth_date = date(*[int(i) for i in (by, bm, bd)]) # suppose that all the parameters is not None
try:
end_date = date(*[int(i) for i in (dy, dm, dd)])
except (TypeError, ValueError): # if death date is None
end_date = date.today()
# is birth date after death date or today; if True == 1, else == 0
is_next_year = ((end_date.month, end_date.day) < (birth_date.month, birth_date.day))
age = end_date.year - birth_date.year - is_next_year
return age
Примените эту функцию к кадру данных по строкам:
df.apply(calc_age, axis=1)
и он возвращает pd.Series с возрастом для всех лиц в годах, если нет пропущенных данных. Вы можете связать его с вашим фреймворком данных:
df['personsAge'] = df.apply(calc_age, axis=1)
Затем добавьте еще один столбец со статусом и результатами печати:
def is_dead(row):
dm = row['diedMonth']
dd = row['diedDay']
dy = row['diedYear']
try:
died = date(*[int(i) for i in (dy, dm, dd)])
return True
except ValueError:
return False
df['is_dead'] = df.apply(is_dead, axis=1)
def print_status(row):
bm = row['bornMonth']
bd = row['bornDay']
by = row['bornYear']
dm = row['diedMonth']
dd = row['diedDay']
dy = row['diedYear']
age = row['personsAge']
print("DOB: "+str(bm)+"/"+str(bd)+"/"+str(by)+" ("+str(age)+" years old)")
if row['is_dead']:
print("*DECEASED: "+str(dm)+"/"+str(dd)+"/"+str(dy))
df.apply(print_status, axis=1)
stdout:
DOB: 8/17/1932 (47 years old)
*DECEASED: 3/22/1980
DOB: 4/12/1950 (68 years old)
Если вам не нравится выбор даты копирования и вставки, замените его на подход datetime
из решения Андрея Портного.
Его гораздо проще преобразовать каждое из этих значений в объекты datetime, а затем выполнить фильтрацию if/elif.
import datetime
bmonth = int(storage.iloc[0]['birthMonth'])
bday = int(storage.iloc[0]['birthDay'])
byear = int(storage.iloc[0]['birthYear'])
died_year = storage.iloc[0]['deathYear']
died_month = storage.iloc[0]['deathMonth']
died_day = storage.iloc[0]['deathDay']
start = datetime.datetime(month = bmonth, day=bday, year=byear)
end = datetime.datetime(month=died_month, day=died_day, year=died_year)
(start-end).days#returns the difference between the days
Вы можете также datetime.now()
значение datetime.now()
.
Надеюсь, что это поможет, это поможет сделать ваш поток лучше.
DataFrame.loc
и избежать циклов. Преобразование даты вdatetime
сделает вычитание очень простым.