Mam dane zapisane w postgreSQL
bazie danych. Pytam o te dane za pomocą Python2.7 i zamieniam je w Pandas DataFrame. Jednak w ostatniej kolumnie tej ramki danych znajduje się słownik (lub lista?) Wartości. DataFrame wygląda następująco:
[1] df
Station ID Pollutants
8809 {"a": "46", "b": "3", "c": "12"}
8810 {"a": "36", "b": "5", "c": "8"}
8811 {"b": "2", "c": "7"}
8812 {"c": "11"}
8813 {"a": "82", "c": "15"}
Muszę podzielić tę kolumnę na osobne kolumny, aby DataFrame wyglądała następująco:
[2] df2
Station ID a b c
8809 46 3 12
8810 36 5 8
8811 NaN 2 7
8812 NaN NaN 11
8813 82 NaN 15
Głównym problemem, który mam, jest to, że listy nie są tej samej długości. Ale wszystkie listy zawierają maksymalnie te same 3 wartości: a, b i c. I zawsze pojawiają się w tej samej kolejności (a pierwsza, b druga, c trzecia).
Poniższy kod UŻYWANY do działania i zwracania dokładnie tego, co chciałem (df2).
[3] df
[4] objs = [df, pandas.DataFrame(df['Pollutant Levels'].tolist()).iloc[:, :3]]
[5] df2 = pandas.concat(objs, axis=1).drop('Pollutant Levels', axis=1)
[6] print(df2)
Uruchomiłem ten kod w zeszłym tygodniu i działał dobrze. Ale teraz mój kod jest uszkodzony i otrzymuję ten błąd z wiersza [4]:
IndexError: out-of-bounds on slice (end)
Nie wprowadziłem żadnych zmian w kodzie, ale teraz pojawia się błąd. Czuję, że jest to spowodowane tym, że moja metoda nie jest solidna ani właściwa.
Wszelkie sugestie lub wskazówki, jak podzielić tę kolumnę list na oddzielne kolumny, będą bardzo mile widziane!
EDYCJA: Myślę, że metody .tolist()
i .apply nie działają na moim kodzie, ponieważ jest to jeden Unicode
ciąg, tj .:
#My data format
u{'a': '1', 'b': '2', 'c': '3'}
#and not
{u'a': '1', u'b': '2', u'c': '3'}
Dane są importowane z postgreSQL
bazy danych w tym formacie. Jakaś pomoc lub pomysły dotyczące tego problemu? czy istnieje sposób na konwersję Unicode
?
źródło
pd.DataFrame(df[col].tolist())
dawna, nigdy o tym nie myślałemapply(pd.Series)
. Bardzo dobrze.DataFrame(df['col'].tolist())
podejście jest o wiele szybsze niż podejście zastosuj!df[col].map(eval)
przed przekształceniem go do DataFrameWiem, że pytanie jest dość stare, ale przyszedłem tutaj, szukając odpowiedzi. Obecnie jest lepszy (i szybszy) sposób na zrobienie tego za pomocą
json_normalize
:import pandas as pd df2 = pd.json_normalize(df['Pollutant Levels'])
Pozwala to uniknąć kosztownych funkcji aplikacji ...
źródło
Spróbuj tego: dane zwrócone z SQL muszą zostać przekonwertowane na Dict. czy może być
"Pollutant Levels"
terazPollutants'
StationID Pollutants 0 8809 {"a":"46","b":"3","c":"12"} 1 8810 {"a":"36","b":"5","c":"8"} 2 8811 {"b":"2","c":"7"} 3 8812 {"c":"11"} 4 8813 {"a":"82","c":"15"} df2["Pollutants"] = df2["Pollutants"].apply(lambda x : dict(eval(x)) ) df3 = df2["Pollutants"].apply(pd.Series ) a b c 0 46 3 12 1 36 5 8 2 NaN 2 7 3 NaN NaN 11 4 82 NaN 15 result = pd.concat([df, df3], axis=1).drop('Pollutants', axis=1) result StationID a b c 0 8809 46 3 12 1 8810 36 5 8 2 8811 NaN 2 7 3 8812 NaN NaN 11 4 8813 82 NaN 15
źródło
Odpowiedź Merlina jest lepsza i super łatwa, ale nie potrzebujemy funkcji lambda. Ocenę słownika można bezpiecznie zignorować na jeden z dwóch poniższych sposobów, jak pokazano poniżej:
Sposób 1: Dwa kroki
# step 1: convert the `Pollutants` column to Pandas dataframe series df_pol_ps = data_df['Pollutants'].apply(pd.Series) df_pol_ps: a b c 0 46 3 12 1 36 5 8 2 NaN 2 7 3 NaN NaN 11 4 82 NaN 15 # step 2: concat columns `a, b, c` and drop/remove the `Pollutants` df_final = pd.concat([df, df_pol_ps], axis = 1).drop('Pollutants', axis = 1) df_final: StationID a b c 0 8809 46 3 12 1 8810 36 5 8 2 8811 NaN 2 7 3 8812 NaN NaN 11 4 8813 82 NaN 15
Sposób 2: Powyższe dwa kroki można połączyć za jednym razem:
df_final = pd.concat([df, df['Pollutants'].apply(pd.Series)], axis = 1).drop('Pollutants', axis = 1) df_final: StationID a b c 0 8809 46 3 12 1 8810 36 5 8 2 8811 NaN 2 7 3 8812 NaN NaN 11 4 8813 82 NaN 15
źródło
Gorąco polecam metodę wyodrębnienia kolumny „Zanieczyszczenia”:
df_pollutants = pd.DataFrame(df['Pollutants'].values.tolist(), index=df.index)
jest znacznie szybszy niż
df_pollutants = df['Pollutants'].apply(pd.Series)
kiedy rozmiar df jest gigantyczny.
źródło
apply
cała ramka danych jest zarządzana przez pandy, ale jeśli chodzi ovalues
to, gra tylko z tym,numpy ndarrays
co jest z natury szybsze ze względu na fakt, że ma czystec
implementacje.Możesz używać
join
zpop
+tolist
. Wydajność jest porównywalnaconcat
zdrop
+tolist
, ale niektórzy mogą uznać tę składnię za czystszą:res = df.join(pd.DataFrame(df.pop('b').tolist()))
Benchmarking innymi metodami:
df = pd.DataFrame({'a':[1,2,3], 'b':[{'c':1}, {'d':3}, {'c':5, 'd':6}]}) def joris1(df): return pd.concat([df.drop('b', axis=1), df['b'].apply(pd.Series)], axis=1) def joris2(df): return pd.concat([df.drop('b', axis=1), pd.DataFrame(df['b'].tolist())], axis=1) def jpp(df): return df.join(pd.DataFrame(df.pop('b').tolist())) df = pd.concat([df]*1000, ignore_index=True) %timeit joris1(df.copy()) # 1.33 s per loop %timeit joris2(df.copy()) # 7.42 ms per loop %timeit jpp(df.copy()) # 7.68 ms per loop
źródło
pd.json_normalize(df.Pollutants)
jest znacznie szybszy niżdf.Pollutants.apply(pd.Series)
%%timeit
poniżej. Dla 1M rzędów.json_normalize
jest 47 razy szybszy niż.apply
.dict
kolumna madict
lubstr
typ.dict
typ przy użyciuast.literal_eval
.pd.json_normalize
do konwersjidicts
, zkeys
nagłówkami ivalues
wierszami.record_path
&meta
) Do obsługi zagnieżdżonychdicts
.pandas.DataFrame.join
do łączenia oryginalnej ramki DataFramedf
z kolumnami utworzonymi przy użyciupd.json_normalize
df.reset_index()
aby uzyskać indeks liczb całkowitych, przed wykonaniem normalizacji i łączenia.pandas.DataFrame.drop
, aby usunąć niepotrzebną kolumnędicts
NaN
, muszą być wypełnione pustymdict
df.Pollutants = df.Pollutants.fillna({i: {} for i in df.index})
'Pollutants'
kolumna jest ciągami, użyj'{}'
.import pandas as pd from ast import literal_eval import numpy as np data = {'Station ID': [8809, 8810, 8811, 8812, 8813, 8814], 'Pollutants': ['{"a": "46", "b": "3", "c": "12"}', '{"a": "36", "b": "5", "c": "8"}', '{"b": "2", "c": "7"}', '{"c": "11"}', '{"a": "82", "c": "15"}', np.nan]} df = pd.DataFrame(data) # display(df) Station ID Pollutants 0 8809 {"a": "46", "b": "3", "c": "12"} 1 8810 {"a": "36", "b": "5", "c": "8"} 2 8811 {"b": "2", "c": "7"} 3 8812 {"c": "11"} 4 8813 {"a": "82", "c": "15"} 5 8814 NaN # replace NaN with '{}' if the column is strings, otherwise replace with {} # df.Pollutants = df.Pollutants.fillna('{}') # if the NaN is in a column of strings df.Pollutants = df.Pollutants.fillna({i: {} for i in df.index}) # if the column is not strings # Convert the column of stringified dicts to dicts # skip this line, if the column contains dicts df.Pollutants = df.Pollutants.apply(literal_eval) # reset the index if the index is not unique integers from 0 to n-1 # df.reset_index(inplace=True) # uncomment if needed # normalize the column of dictionaries and join it to df df = df.join(pd.json_normalize(df.Pollutants)) # drop Pollutants df.drop(columns=['Pollutants'], inplace=True) # display(df) Station ID a b c 0 8809 46 3 12 1 8810 36 5 8 2 8811 NaN 2 7 3 8812 NaN NaN 11 4 8813 82 NaN 15 5 8814 NaN NaN NaN
%%timeit
# dataframe with 1M rows dfb = pd.concat([df]*200000).reset_index(drop=True) %%timeit dfb.join(pd.json_normalize(dfb.Pollutants)) [out]: 5.44 s ± 32.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) %%timeit pd.concat([dfb.drop(columns=['Pollutants']), dfb.Pollutants.apply(pd.Series)], axis=1) [out]: 4min 17s ± 2.44 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
źródło
Jedno rozwiązanie liniowe jest następujące:
>>> df = pd.concat([df['Station ID'], df['Pollutants'].apply(pd.Series)], axis=1) >>> print(df) Station ID a b c 0 8809 46 3 12 1 8810 36 5 8 2 8811 NaN 2 7 3 8812 NaN NaN 11 4 8813 82 NaN 15
źródło
my_df = pd.DataFrame.from_dict(my_dict, orient='index', columns=['my_col'])
.. poprawnie przeanalizowałby dyktando (umieszczając każdy klucz dykt w osobnej kolumnie df, a wartości kluczy w wierszach df), więc w pierwszej kolejności dykty nie zostałyby zgniecione w jednej kolumnie.
źródło
Połączyłem te kroki w metodzie, musisz przekazać tylko ramkę danych i kolumnę zawierającą dyktę do rozwinięcia:
def expand_dataframe(dw: pd.DataFrame, column_to_expand: str) -> pd.DataFrame: """ dw: DataFrame with some column which contain a dict to expand in columns column_to_expand: String with column name of dw """ import pandas as pd def convert_to_dict(sequence: str) -> Dict: import json s = sequence json_acceptable_string = s.replace("'", "\"") d = json.loads(json_acceptable_string) return d expanded_dataframe = pd.concat([dw.drop([column_to_expand], axis=1), dw[column_to_expand] .apply(convert_to_dict) .apply(pd.Series)], axis=1) return expanded_dataframe
źródło
df = pd.concat([df['a'], df.b.apply(pd.Series)], axis=1)
źródło
>>> df Station ID Pollutants 0 8809 {"a": "46", "b": "3", "c": "12"} 1 8810 {"a": "36", "b": "5", "c": "8"} 2 8811 {"b": "2", "c": "7"} 3 8812 {"c": "11"} 4 8813 {"a": "82", "c": "15"}
porównanie szybkości dla dużego zbioru danych zawierającego 10 milionów wierszy
>>> df = pd.concat([df]*100000).reset_index(drop=True) >>> df = pd.concat([df]*20).reset_index(drop=True) >>> print(df.shape) (10000000, 2)
def apply_drop(df): return df.join(df['Pollutants'].apply(pd.Series)).drop('Pollutants', axis=1) def json_normalise_drop(df): return df.join(pd.json_normalize(df.Pollutants)).drop('Pollutants', axis=1) def tolist_drop(df): return df.join(pd.DataFrame(df['Pollutants'].tolist())).drop('Pollutants', axis=1) def vlues_tolist_drop(df): return df.join(pd.DataFrame(df['Pollutants'].values.tolist())).drop('Pollutants', axis=1) def pop_tolist(df): return df.join(pd.DataFrame(df.pop('Pollutants').tolist())) def pop_values_tolist(df): return df.join(pd.DataFrame(df.pop('Pollutants').values.tolist()))
>>> %timeit apply_drop(df.copy()) 1 loop, best of 3: 53min 20s per loop >>> %timeit json_normalise_drop(df.copy()) 1 loop, best of 3: 54.9 s per loop >>> %timeit tolist_drop(df.copy()) 1 loop, best of 3: 6.62 s per loop >>> %timeit vlues_tolist_drop(df.copy()) 1 loop, best of 3: 6.63 s per loop >>> %timeit pop_tolist(df.copy()) 1 loop, best of 3: 5.99 s per loop >>> %timeit pop_values_tolist(df.copy()) 1 loop, best of 3: 5.94 s per loop
+---------------------+-----------+ | apply_drop | 53min 20s | | json_normalise_drop | 54.9 s | | tolist_drop | 6.62 s | | vlues_tolist_drop | 6.63 s | | pop_tolist | 5.99 s | | pop_values_tolist | 5.94 s | +---------------------+-----------+
źródło