Pandas мультииндекс против «вложенных фреймов данных» (DataFrame в DataFrame) для объединения DataFrames - PullRequest
0 голосов
/ 15 января 2020

Я работал над этим уже несколько недель, и ничто из того, что я нашел, пока не кажется идеальным решением. Однако я хотел бы поделиться своим прогрессом до того, как мы перейдем к моему вопросу.

Мой набор данных

В основном у меня есть три вида таблиц, которые мне нужно присоединить к разумному структура данных с использованием 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" так что мне интересно, может ли кто-нибудь придумать лучшее решение для этого.

...