Я работал над этим уже несколько недель, и ничто из того, что я нашел, пока не кажется идеальным решением. Однако я хотел бы поделиться своим прогрессом до того, как мы перейдем к моему вопросу.
Мой набор данных
В основном у меня есть три вида таблиц, которые мне нужно присоединить к разумному структура данных с использованием pandas
. Я буду использовать общий пример данных о погоде c, чтобы объяснить мою проблему, имея в виду, что мой набор данных намного больше:
Таблица 1 содержит общие данные о метеостанциях в виде DataFrame, что-то вроде
df = pd.DataFrame([
[0, "Frankfurt", True, "Germany"],
[1, "New York", True, "USA"],
[2, "Death Valley", False, "USA"]],
columns=['ID', 'City', 'Urban', "Country"]).set_index("ID")
Имейте в виду, что мой набор данных содержит гораздо больше столбцов.
Таблица 2 имеет многоиндексный индекс и хранит ежедневные данные о погоде, например,
daily = []
for ID in range(3):
for date in pd.date_range(start='1/1/2018', periods=5):
daily.append({"ID": ID,
"date": date,
"rainfall": np.random.rand(),
"sun_hours": np.random.randint(12)})
daily_df = pd.DataFrame(daily).set_index(["ID", "date"])
И снова здесь мой набор данных содержит данные за 30 лет.
Таблица 3 содержит также многоиндексные данные с тем же индексом, но содержит только годовые данные
annual = []
for ID in range(3):
for date in pd.date_range(start='1/1/2018', periods=2, freq='Y'):
annual.append({"ID": ID, "date": date, "population": np.random.randint(1e6)})
annual_df = pd.DataFrame(annual).set_index(["ID", "date"])
Здесь мой набор данных имеет примерно 30 столбцов. Обратите внимание, что могут быть годовые данные о датах, когда нет ежедневных данных, и наоборот. для меня важно, чтобы сохранить информацию о погоде, в которой отсутствовала точка данных, т.е. NaN в таблице 2 или 3.
Что мне нужно
Структура данных, объединяющая эти кадры данных в один разумный, чистый кадр.
Мой первый подход - объединить эти кадры в стиле sql:
df.join(daily_df, how='outer').join(anual_df, how='outer')
Это даст мне что-то вроде этого:
City Urban Country rainfall sun_hours population
ID date
0 2018-01-01 Frankfurt True Germany 0.435739 3.0 NaN
2018-01-02 Frankfurt True Germany 0.978759 5.0 NaN
2018-01-03 Frankfurt True Germany 0.705686 7.0 NaN
2018-01-04 Frankfurt True Germany 0.724486 3.0 NaN
2018-01-05 Frankfurt True Germany 0.203595 0.0 NaN
2018-12-31 NaN NaN NaN NaN NaN 201597.0
2019-12-31 NaN NaN NaN NaN NaN 447367.0
1 2018-01-01 New York True USA 0.017872 11.0 NaN
2018-01-02 New York True USA 0.977488 6.0 NaN
2018-01-03 New York True USA 0.601578 1.0 NaN
2018-01-04 New York True USA 0.262022 9.0 NaN
2018-01-05 New York True USA 0.920286 7.0 NaN
2018-12-31 NaN NaN NaN NaN NaN 708109.0
2019-12-31 NaN NaN NaN NaN NaN 390909.0
2 2018-01-01 Death Valley False USA 0.313836 9.0 NaN
2018-01-02 Death Valley False USA 0.581446 2.0 NaN
2018-01-03 Death Valley False USA 0.949100 5.0 NaN
2018-01-04 Death Valley False USA 0.928053 9.0 NaN
2018-01-05 Death Valley False USA 0.259972 0.0 NaN
2018-12-31 NaN NaN NaN NaN NaN 860694.0
2019-12-31 NaN NaN NaN NaN NaN 613661.0
(Кстати: я использую SQLAlchemy для моделирования своей базы данных, и выходные данные моих запросов во многом похожи на результат двух внешних объединений)
Теперь, как вы можете видеть, это выглядит довольно грязно, избыточный, тонн NaNs. Даже если данные эффективно хранятся в фоновом режиме pandas, у меня есть проблемы с этим, например, когда я получаю доступ, например, к годовым данным за несколько лет:
In[11]:
df.loc[0]["population"]["2018-01-01":"2020-01-01"]
Out[11]:
date
2018-01-01 NaN
2018-01-02 NaN
2018-01-03 NaN
2018-01-04 NaN
2018-01-05 NaN
2018-12-31 456843.0
2019-12-31 64436.0
, что легко решается добавлением .dropna()
но я чувствую, что в этом нет необходимости, плюс я теряю информацию, была ли эта точка данных пропущена или вывод был просто раздут из-за беспорядка операции соединения. (Что касается моего приложения, обходные пути пока утомительны)
Что мне еще нужно, что я не получаю от этого решения:
1) Способ доступа к данным Например, "population"
только с датами, которые изначально были в annual_df
.
2) Способ одновременного доступа ко всем "daily"
или "annual"
столбцам для данного идентификатора.
Мое грязное решение
Я могу делать DataFrames в DataFrames. Существуют способы построения таких вложенных DataFrames с функциями groupby, aggregation и lambda в одной строке, но я просто склею их для лучшей читаемости:
df["daily"] = pd.DataFrame([
[0, daily_df.loc[0]],
[1, daily_df.loc[1]],
[2, daily_df.loc[2]]], columns = ["ID", "daily"]).set_index("ID")
И аналогично для year, что приводит к следующему:
Out[196]:
City Urban Country daily annual
ID
0 Frankfurt True Germany ... ...
1 New York True USA ... ...
2 Death Valley False USA ... ...
Теперь я могу получить доступ к тому, что я хотел бы:
In[205]:
df.loc[0]["annual"]["2018-01-01":"2020-01-01"]
Out[205]:
population
date
2018-12-31 393541
2019-12-31 139129
И есть только значения NaN, где данные фактически отсутствуют, что решает 1). Наконец, 2) также решается следующим образом:
In[226]
df.loc[0]["daily"]
Out[226]:
rainfall sun_hours
date
2018-01-01 0.498966 6
2018-01-02 0.814759 3
2018-01-03 0.070201 0
2018-01-04 0.228093 10
2018-01-05 0.538463 0
Мой актуальный вопрос
Однако все, что вы читаете о DataFrames в DataFrames в inte rnet, в основном "нет, нет, Multiindex" так что мне интересно, может ли кто-нибудь придумать лучшее решение для этого.