Wybierz wiersze w ramce danych pandy MultiIndex
Jakie są najczęstsze sposoby wybierania / filtrowania wierszy ramki danych, której indeks jest MultiIndex?
- krojenie na podstawie pojedynczej wartości / etykiety
- krojenie na podstawie wielu etykiet z jednego lub więcej poziomów
- filtrowanie na warunkach logicznych i wyrażeniach
- jakie metody mają zastosowanie w jakich okolicznościach
Założenia dla prostoty:
- ramka danych wejściowych nie posiada zduplikowane klucze indeksu
- ramka danych wejściowych poniżej ma tylko dwa poziomy. (Większość rozwiązań pokazanych tutaj uogólnia się na N poziomów)
Przykładowe wejście:
mux = pd.MultiIndex.from_arrays([ list('aaaabbbbbccddddd'), list('tuvwtuvwtuvwtuvw') ], names=['one', 'two']) df = pd.DataFrame({'col': np.arange(len(mux))}, mux) col one two a t 0 u 1 v 2 w 3 b t 4 u 5 v 6 w 7 t 8 c u 9 v 10 d w 11 t 12 u 13 v 14 w 15
Pytanie 1: Wybór pojedynczego elementu
Jak wybrać wiersze posiadające " a "na poziomie"1"?
col
one two
a t 0
u 1
v 2
w 3
Dodatkowo, jak mógłbym być w stanie obniżyć poziom "jeden" w wyjściu?
col
two
t 0
u 1
v 2
w 3
Pytanie 1b
Jak pokroić wszystkie wiersze z wartością "t" na poziomie "dwa"?
col
one two
a t 0
b t 4
t 8
d t 12
Pytanie 2: Wybór wielu wartości na poziomie
Jak mogę wybrać wiersze odpowiadające elementom " b " I " d "na poziomie"pierwszy"?
col
one two
b t 4
u 5
v 6
w 7
t 8
d w 11
t 12
u 13
v 14
w 15
Pytanie 2b
Jak uzyskać wszystkie wartości odpowiadające " t " i " w "na poziomie"drugim"?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
Pytanie 3: krojenie pojedynczego przekroju (x, y)
Jak odzyskać przekrój, tzn. pojedynczy wiersz o określonej wartości dla indeksu z df
? W szczególności, jak Pobierz przekrój ('c', 'u')
, podany przez
col
one two
c u 9
Pytanie 4: Krojenie Wielu Przekrojów [(a, b), (c, d), ...]
Jak wybrać dwa wiersze odpowiadające ('c', 'u')
i ('a', 'w')
?
col
one two
c u 9
a w 3
Pytanie 5: jeden element pokrojony na poziom
Jak mogę pobrać wszystkie wiersze odpowiadające " a "na poziomie" pierwszy " lub " t "na poziomie"drugi"?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
Pytanie 6: Dowolne Krojenie]}
Jak pokroić konkretne przekroje? Dla " a " i " b " chciałbym wybierz wszystkie wiersze z podpoziomem " u " I "v", A dla" d "chciałbym wybrać wiersze z podpoziomem"w".
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
Pytanie 7 użyje unikalnej konfiguracji składającej się z poziomu liczbowego:
np.random.seed(0) mux2 = pd.MultiIndex.from_arrays([ list('aaaabbbbbccddddd'), np.random.choice(10, size=16) ], names=['one', 'two']) df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2) col one two a 5 0 0 1 3 2 3 3 b 7 4 9 5 3 6 5 7 2 8 c 4 9 7 10 d 6 11 8 12 8 13 1 14 6 15
Pytanie 7: filtrowanie przez nierówności liczbowe na poszczególnych poziomach multiindex
Jak uzyskać wszystkie wiersze, w których wartości na poziomie "dwa" są większe niż 5?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
Uwaga: Ten post będzie Nie przejść przez jak tworzyć Multiindexy, jak wykonywać operacje przyporządkowania na nich, lub wszelkie dyskusje związane z wydajnością (są to oddzielne tematy na inny raz).
2 answers
MultiIndex / Zaawansowane Indeksowanie
[144]} oto wprowadzenie do niektórych popularnych idiomów (odtąd zwanych czterema idiomami), które będziemy często odwiedzać [148]}Uwaga
Ten post będzie zorganizowany w następujący sposób:
- pytania postawione w PO będą rozwiązywane, jeden po drugim
- dla każdego pytania zostanie zademonstrowana jedna lub więcej metod mających zastosowanie do rozwiązania tego problemu i uzyskania oczekiwanego wyniku.
Uwagas (podobnie jak ta) zostanie dołączona dla czytelników zainteresowanych nauką o dodatkowych funkcjonalnościach, szczegółach implementacji, i inne info pobieżne do omawianego tematu. Te notatki zostały skompilowane poprzez przeszukiwanie dokumentów i odkrywanie różnych niejasnych funkcji, oraz z własnego (co prawda ograniczonego) doświadczenia.
Wszystkie próbki kodu zostały stworzone i przetestowane na pandas v0.23.4, python3. 7. Jeśli coś nie jest jasne, lub faktyczne nieprawidłowe, lub jeśli nie znajdź rozwiązanie odpowiednie do twojego przypadku użycia, prosimy o zaproponuj edycję, poproś o wyjaśnienie w komentarzach lub Otwórz nowy pytanie.....
-
DataFrame.loc
- ogólne rozwiązanie wyboru według etykiety (+pd.IndexSlice
dla bardziej złożonych aplikacji obejmujących plastry) -
DataFrame.xs
- ekstrakt określony przekrój z serii / ramki danych. -
DataFrame.query
- określa operacje wycinania i/lub filtrowania dynamicznie (np. jako wyrażenie, które jest dynamicznie oceniane. Ma większe zastosowanie w niektórych scenariuszach niż w innych. Zobacz również tę sekcję dokumentów do zapytań o Multiindexy. -
Indeksowanie logiczne z maską generowaną przy użyciu
MultiIndex.get_level_values
(często w połączeniu zIndex.isin
, szczególnie przy filtrowaniu z wieloma wartościami). Jest to również bardzo przydatne w niektórych okolicznościach.
Możesz użyćPytanie 1
Jak wybrać wiersze posiadające "a "na poziomie"1"?
col one two a t 0 u 1 v 2 w 3
, jako rozwiązania ogólnego przeznaczenia, mającego zastosowanie w większości sytuacji:
df.loc[['a']]
W tym momencie, jeśli otrzymasz
TypeError: Expected tuple, got str
To oznacza, że używasz starszej wersji pand. Rozważ modernizację! W przeciwnym razie użyj df.loc[('a', slice(None)), :]
.
Alternatywnie, możesz użyć xs
tutaj, ponieważ wydobywamy pojedynczy przekrój. Zwróć uwagę na argumenty levels
i axis
(można tu założyć rozsądne wartości domyślne).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
Tutaj, drop_level=False
argument jest potrzebny, aby zapobiec xs
spadaniu poziomu "jeden" w wyniku (poziom, na którym się pokroiliśmy).
Jeszcze jedną opcją jest użycie query
:
df.query("one == 'a'")
Jeśli indeks nie ma nazwy, musisz zmienić łańcuch zapytania, aby był "ilevel_0 == 'a'"
.
Wreszcie, używając get_level_values
:
df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']
Dodatkowo, jak mógłbym być w stanie obniżyć poziom "jeden" w wyjściu?
col two t 0 u 1 v 2 w 3
To może być łatwo zrobione za pomocą
df.loc['a'] # Notice the single string argument instead the list.
Lub,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
Zauważ, że możemy pominąć argument drop_level
(domyślnie zakłada się, że to True
).
Uwaga
Można zauważyć, że przefiltrowana ramka danych może nadal mieć wszystkie poziomy, nawet jeśli nie pokazują podczas drukowania ramki danych. Na przykład,v = df.loc[['a']] print(v) col one two a t 0 u 1 v 2 w 3 print(v.index) MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']], labels=[[0, 0, 0, 0], [0, 1, 2, 3]], names=['one', 'two'])
Możesz pozbyć się tych poziomów za pomocą
MultiIndex.remove_unused_levels
:v.index = v.index.remove_unused_levels()
print(v.index) MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']], labels=[[0, 0, 0, 0], [0, 1, 2, 3]], names=['one', 'two'])
Pytanie 1b
Jak pokroić wszystkie wiersze z wartością " t "na poziomie "dwa"?
col one two a t 0 b t 4 t 8 d t 12
Intuicyjnie, chciałbyś coś angażującego slice()
:
df.loc[(slice(None), 't'), :]
To Po Prostu Działa! Ale jest niezgrabny. Możemy ułatwić bardziej naturalne krojenie składnia przy użyciu API pd.IndexSlice
tutaj.
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
To jest dużo, dużo czystsze.
Uwaga
Dlaczego wymagany jest końcowy kawałek:
w kolumnach? To dlatego,loc
może być używany do zaznaczania i wycinania wzdłuż obu osi (axis=0
lubaxis=1
). Bez wyraźnego określenia, która oś krojenia ma być zrobione na, operacja staje się niejednoznaczna. Zobacz duże czerwone pudełko w dokumentacji na temat krojenia.Jeśli chcesz usunąć każdy cień niejednoznaczności,
loc
przyjmujeaxis
parametr:df.loc(axis=0)[pd.IndexSlice[:, 't']]
Bez parametru
axis
(tzn. po prostu wykonującdf.loc[pd.IndexSlice[:, 't']]
) zakłada się, że na kolumnach, iKeyError
zostanie podniesiony w tej sytuacji.Jest to udokumentowane w krajalnice . Na potrzeby tego postu będziemy jednak wyraźnie określać wszystkie osie.
Z xs
, jest
df.xs('t', axis=0, level=1, drop_level=False)
Z query
, jest
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
I wreszcie, z get_level_values
, ty may do
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
/ Align = "left" /
Pytanie 2
Jak Mogę wybrać wiersze odpowiadające elementom " b " I " d "na poziomie"pierwszy"?
col one two b t 4 u 5 v 6 w 7 t 8 d w 11 t 12 u 13 v 14 w 15
Używając loc, robi się to w podobny sposób poprzez podanie listy.
df.loc[['b', 'd']]
Aby rozwiązać powyższy problem wyboru "b" I "d", możesz również użyć query
:
items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')
Uwaga
Tak, domyślnym parserem jest'pandas'
, ale ważne jest, aby wyróżnić ta składnia nie jest konwencjonalnie Pythonem. Na Pandy parser generuje nieco inne drzewo Parsów od ekspresja. Ma to na celu uczynienie niektórych operacji bardziej intuicyjnymi do sprecyzuj. Aby uzyskać więcej informacji, przeczytaj mój post na dynamiczna ocena ekspresji w pandach za pomocą pd.eval () .
I, z get_level_values
+ Index.isin
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
Pytanie 2b
Jak uzyskać wszystkie wartości odpowiadające" t "i" w " w poziomie "dwa"?
col one two a t 0 w 3 b t 4 w 7 t 8 d w 11 t 12 w 15
Z loc
, jest to możliwe tylko w połączeniu z pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
Pierwszy dwukropek :
w pd.IndexSlice[:, ['t', 'w']]
oznacza przecięcie pierwszego poziomu. Wraz ze wzrostem głębokości pytanego poziomu, będziesz musiał podać więcej plasterków, po jednym na każdy poziom. Nie musisz jednak podawać więcej poziomów poza ten, który jest pokrojony.
Z query
, to jest
items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas')
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')
Z get_level_values
i Index.isin
(podobnie jak wyżej):
df[df.index.get_level_values('two').isin(['t', 'w'])]
Pytanie 3
Jak odzyskać przekrój, czyli pojedynczy wiersz o określonej wartości dla indeksu z
df
? Konkretnie, jak odzyskać krzyż sekcja('c', 'u')
, podana przezcol one two c u 9
Użyj loc
przez podanie krotki kluczy:
df.loc[('c', 'u'), :]
Lub,
df.loc[pd.IndexSlice[('c', 'u')]]
Jest to pierwszy argument, w którym wszystkie pozostałe argumenty są ustawione na odpowiednie wartości domyślne:]}Uwaga
W tym momencie możesz natknąć się naPerformanceWarning
to wygląda tak:PerformanceWarning: indexing past lexsort depth may impact performance.
To oznacza, że Twój indeks nie jest posortowany. pandy zależą od sortowania indeksu (w tym przypadku leksykograficznie, ponieważ mamy do czynienia z wartościami ciągów) w celu optymalnego wyszukiwania i wyszukiwania. Szybkim rozwiązaniem byłoby posortowanie DataFrame z góry za pomocą
DataFrame.sort_index
. Jest to szczególnie pożądane z punktu widzenia wydajności, jeśli planujesz robić wiele takich zapytań w tandemie:df_sort = df.sort_index() df_sort.loc[('c', 'u')]
Możesz również użyć
MultiIndex.is_lexsorted()
aby sprawdzić, czy indeks jest uporządkowane czy nie. Funkcja zwraca odpowiednioTrue
lubFalse
. Można wywołać tę funkcję, aby określić, czy dodatkowe sortowanie krok jest wymagany lub nie.
df.xs(('c', 'u'))
Z query
, rzeczy stają się nieco niezgrabne:
df.query("one == 'c' and two == 'u'")
Widzisz teraz, że będzie to stosunkowo trudne do uogólnienia. Ale nadal jest OK dla tego konkretnego problemu.
Z dostępami na wielu poziomach można nadal korzystać, ale nie jest to zalecane:]}m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
Pytanie 4
Jak wybrać dwa wiersze odpowiadające
('c', 'u')
i('a', 'w')
?col one two c u 9 a w 3
Z loc
, to jest jeszcze tak proste jak:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
Z query
, będziesz musiał dynamicznie wygenerować ciąg zapytania przez iterowanie nad przekrojami i poziomami:
cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses)
query = '(' + ') or ('.join([
' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)])
for cs in cses
]) + ')'
print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))
df.query(query)
W 100% NIE POLECAM! Ale to możliwe.
Co zrobić, jeśli mam wiele poziomów?
Jedną z opcji w tym scenariuszu byłoby użycie droplevel
aby upuścić poziomy, których nie sprawdzasz, użyj isin
aby przetestować członkostwo, a następnie indeks boolean na wynik końcowy.
df[df.index.droplevel(unused_level).isin([('c', 'u'), ('a', 'w')])]
Pytanie 5
Jak mogę odzyskać wszystkie wiersze odpowiadające " a " w poziomie "jeden" lub "t" na poziomie "2"?
col one two a t 0 u 1 v 2 w 3 b t 4 t 8 d t 12
Jest to w rzeczywistości bardzo trudne do zrobienia z loc
przy jednoczesnym zapewnieniu poprawności i przy zachowaniu przejrzystości kodu. df.loc[pd.IndexSlice['a', 't']]
jest niepoprawne, interpretuje się je jako df.loc[pd.IndexSlice[('a', 't')]]
(tzn. wybierając przekrój). Można pomyśleć o rozwiązaniu z pd.concat
do obsługi każdej etykiety osobno:
pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
col
one two
a t 0
u 1
v 2
w 3
t 0 # Does this look right to you? No, it isn't!
b t 4
t 8
d t 12
Ale zauważysz, że jeden z wierszy jest duplikowany. Rząd ten spełniał bowiem oba warunki krojenia i tak pojawił się dwukrotnie. Zamiast tego będziesz musiał zrobić
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
Ale jeśli ramka danych z natury zawiera zduplikowane indeksy( które chcesz), to nie zachowa ich. stosować z najwyższą ostrożnością.
Z query
, to jest głupio proste:
df.query("one == 'a' or two == 't'")
Z get_level_values
, to nadal proste, ale nie tak eleganckie:
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
Pytanie 6
Jak pokroić konkretne przekroje? Dla "a" i " b "chciałbym wybrać wszystkie wiersze z podpoziomami" u" i "v", oraz dla "d" chciałbym wybrać wiersze z podpoziomem "w".
col one two a u 1 v 2 b u 5 v 6 d w 11 w 15
Jest to szczególny przypadek, który dodałem, aby pomóc zrozumieć przydatność czterech idiomów-jest to jeden przypadek, w którym żaden z nich nie będzie działał skutecznie, ponieważ krojenie jest {169]}bardzo {172]} specyficzne i nie podąża za żadnym prawdziwym wzorcem.
Zazwyczaj takie problemy wymagają jawnego przekazania listy kluczy do loc
. Jednym ze sposobów jest z:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
Jeśli chcesz zachować trochę pisania, rozpoznasz, że istnieje wzór do krojenia "a", " b " i jego podpoziomów, więc możemy rozdzielić zadanie krojenia na dwie części i concat
wynik:
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
Specyfikacja krojenia dla "a" i "b" jest nieco czystsza (('a', 'b'), ('u', 'v'))
, ponieważ te same poziomy indeksowane są takie same dla każdego poziomu.
Pytanie 7
Jak uzyskać wszystkie wiersze, w których wartości na poziomie "dwa" są większe niż 5?
col one two b 7 4 9 5 c 7 10 d 6 11 8 12 8 13 6 15
Można to zrobić za pomocą query
,
df2.query("two > 5")
I get_level_values
.
df2[df2.index.get_level_values('two') > 5]
Uwaga
Podobnie jak w tym przykładzie, możemy filtrować na podstawie dowolnego warunku za pomocą tych konstrukcji. Ogólnie rzecz biorąc, warto pamiętać, żeloc
ixs
są specjalnie przeznaczone do indeksowania opartego na etykietach, podczas gdyquery
iget_level_values
są pomocne w budowaniu ogólnych masek warunkowych do filtrowania.
Bonus Pytanie
Co jeśli będę musiał pokroić
MultiIndex
kolumna ?
Właściwie, większość rozwiązań tutaj stosuje się również do kolumn, z niewielkimi zmianami. Rozważyć:
np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
list('ABCD'), list('efgh')
], names=['one','two'])
df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)
one A B C D
two e f g h e f g h e f g h e f g h
0 5 0 3 3 7 9 3 5 2 4 7 6 8 8 1 6
1 7 7 8 1 5 9 8 9 4 3 0 3 5 0 2 3
2 8 1 3 3 3 7 0 1 9 9 0 4 7 3 2 7
Są to następujące zmiany, które musisz wprowadzić do czterech idiomów, aby mogły pracować z kolumnami.
-
Aby pokroić z
loc
, użyjdf3.loc[:, ....] # Notice how we slice across the index with `:`.
Lub,
df3.loc[:, pd.IndexSlice[...]]
-
Aby użyć
xs
, wystarczy podać argumentaxis=1
. -
Możesz uzyskać dostęp do wartości poziomu kolumn bezpośrednio za pomocą
df.columns.get_level_values
. Następnie będziesz musiał zrobić coś takiego jakdf.loc[:, {condition}]
Gdzie
{condition}
reprezentuje pewien warunek zbudowany przy użyciucolumns.get_level_values
. -
Aby użyć
query
, jedyną opcją jest transpozycja, Zapytanie o indeks i transpozycja ponownie:df3.T.query(...).T
Nie zaleca się, użyj jednej z pozostałych 3 opcji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-26 11:27:15
Ostatnio natknąłem się na przypadek użycia, w którym miałem wielopoziomową ramkę danych 3+, w której nie mogłem sprawić, by żadne z powyższych rozwiązań przyniosło wyniki, których szukałem. Jest całkiem możliwe, że powyższe rozwiązania oczywiście działają w moim przypadku użycia, a ja próbowałem kilku, jednak nie byłem w stanie zmusić ich do pracy z czasem, który miałem dostępny.
Jestem daleki od eksperta, ale natknąłem się na rozwiązanie, które nie było wymienione w wyczerpujących odpowiedziach powyżej. Nie gwarantuję, że rozwiązania są w jakikolwiek sposób optymalne.
Jest to inny sposób na uzyskanie nieco innego wyniku niż pytanie # 6 powyżej. (i prawdopodobnie również inne pytania)
Konkretnie szukałam:
- sposób wyboru dwóch + wartości z jednego poziomu indeksu i jednej wartości z innego poziomu indeksu oraz
- sposób na pozostawienie wartości indeksu z poprzedniej operacji na wyjściu ramki danych.
Jako Małpi klucz w zębatkach (jednak całkowicie naprawialny):
- indeksy były nienazwane.
Na ramce danych zabawki poniżej:
index = pd.MultiIndex.from_product([['a','b'],
['stock1','stock2','stock3'],
['price','volume','velocity']])
df = pd.DataFrame([1,2,3,4,5,6,7,8,9,
10,11,12,13,14,15,16,17,18],
index)
0
a stock1 price 1
volume 2
velocity 3
stock2 price 4
volume 5
velocity 6
stock3 price 7
volume 8
velocity 9
b stock1 price 10
volume 11
velocity 12
stock2 price 13
volume 14
velocity 15
stock3 price 16
volume 17
velocity 18
Korzystanie z poniższych prac, oczywiście:
df.xs(('stock1', 'velocity'), level=(1,2))
0
a 3
b 12
Ale chciałem inny wynik, więc moja metoda, aby uzyskać ten wynik była:
df.iloc[df.index.isin(['stock1'], level=1) &
df.index.isin(['velocity'], level=2)]
0
a stock1 velocity 3
b stock1 velocity 12
I gdybym chciał dwie wartości+ z jednego poziomu i jedną (lub 2+) wartość z innego poziomu:
df.iloc[df.index.isin(['stock1','stock3'], level=1) &
df.index.isin(['velocity'], level=2)]
0
a stock1 velocity 3
stock3 velocity 9
b stock1 velocity 12
stock3 velocity 18
Powyższa metoda jest prawdopodobnie trochę niezgrabna, jednak okazało się, że spełnia moje potrzeby i jako bonus było mi łatwiej zrozumieć i czytać.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-04 10:38:57