import re
text = "Contact us at info@example.com or sales@example.com"
emails = re.findall(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
print(emails)
# ['info@example.com', 'sales@example.com']['info@example.com', 'sales@example.com']
Wyrażenia regularne (regex) to wzorce tekstowe służące do wyszukiwania, dopasowywania i manipulowania ciągami znaków. W Pythonie obsługuje je wbudowany moduł re, a Pandas integruje je bezpośrednio w operacjach na kolumnach tekstowych przez akcesor .str.
Przed wyrażeniem regularnym zawsze stosujemy prefiks r (raw string), aby backslash \ nie był interpretowany jako znak ucieczki Pythona.
import re
text = "Contact us at info@example.com or sales@example.com"
emails = re.findall(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
print(emails)
# ['info@example.com', 'sales@example.com']['info@example.com', 'sales@example.com']
Oto najważniejsze elementy składni wyrażeń regularnych:
Metaznaki:
| Metaznak | Znaczenie |
|---|---|
. |
Dowolny znak (oprócz \n) |
^ |
Początek ciągu |
$ |
Koniec ciągu |
* |
0 lub więcej powtórzeń |
+ |
1 lub więcej powtórzeń |
? |
0 lub 1 powtórzenie |
\| |
Alternatywa (lub) |
() |
Grupa przechwytująca |
[] |
Zbiór znaków |
{} |
Dokładna liczba powtórzeń |
Klasy znaków:
| Skrót | Znaczenie | Odpowiednik ASCII |
|---|---|---|
\d |
Cyfra | [0-9] |
\D |
Nie-cyfra | [^0-9] |
\w |
Znak „słowny” | [a-zA-Z0-9_] |
\W |
Nie-słowny | [^a-zA-Z0-9_] |
\s |
Biały znak | [ \t\n\r\f\v] |
\S |
Nie-biały znak | [^ \t\n\r\f\v] |
\b |
Granica słowa | — |
Kwantyfikatory:
| Wzorzec | Znaczenie |
|---|---|
a{3} |
Dokładnie 3 wystąpienia a |
a{2,5} |
Od 2 do 5 wystąpień a |
a{2,} |
Co najmniej 2 wystąpienia a |
a*? |
0 lub więcej (leniwie) |
a+? |
1 lub więcej (leniwie) |
import re
text = "<b>bold</b> and <i>italic</i>"
# zachłanne — dopasuje jak najwięcej
greedy = re.findall(r"<.+>", text)
print("Zachłanne:", greedy)
# leniwe — dopasuje jak najmniej
lazy = re.findall(r"<.+?>", text)
print("Leniwe:", lazy)Zachłanne: ['<b>bold</b> and <i>italic</i>']
Leniwe: ['<b>', '</b>', '<i>', '</i>']
reOto tabela w języku Markdown wyjaśniająca kluczowe funkcje modułu re:
| Funkcja | Opis |
|---|---|
re.search() |
Szuka pierwszego dopasowania w całym ciągu. Zwraca obiekt Match lub None. |
re.match() |
Szuka dopasowania tylko na początku ciągu. |
re.findall() |
Zwraca listę wszystkich dopasowań (jako ciągi znaków). |
re.finditer() |
Jak findall, ale zwraca iterator obiektów Match. |
re.sub() |
Zastępuje dopasowania nowym ciągiem lub wynikiem funkcji. |
re.split() |
Dzieli ciąg wg wzorca (bardziej elastyczny niż str.split()). |
re.fullmatch() |
Sprawdza, czy cały ciąg pasuje do wzorca — idealne do walidacji. |
re.compile() |
Kompiluje wzorzec do wielokrotnego użytku — przyspiesza działanie. |
import re
text = "Temperature is 36.6 degrees"
match = re.search(r"\d+\.\d+", text)
if match:
print(f"Znaleziono: {match.group()} na pozycji {match.span()}")
log = """
ERROR 2025-03-06 10:15:32 Connection timeout
INFO 2025-03-06 10:15:33 Retry attempt
ERROR 2025-03-06 10:15:35 Disk full
"""
errors = re.findall(r"ERROR\s+\S+\s+\S+\s+(.+)", log)
print("Błędy:", errors)Znaleziono: 36.6 na pozycji (15, 19)
Błędy: ['Connection timeout', 'Disk full']
Nawiasy okrągłe tworzą grupy — umożliwiają wyodrębnianie fragmentów dopasowania. Grupy mogą być numerowane, nazwane ((?P<name>...)) lub nieprzechwytujące ((?:...)).
import re
# Grupy numerowane
text = "2025-03-06"
match = re.search(r"(\d{4})-(\d{2})-(\d{2})", text)
if match:
print(f"Rok: {match.group(1)}, Miesiąc: {match.group(2)}, Dzień: {match.group(3)}")
# Grupy nazwane
pattern = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"
match = re.search(pattern, "Meeting on 2025-03-06 at noon")
if match:
print(match.groupdict())
# Grupa nieprzechwytująca
result = re.findall(r"(?:https?|ftp)://\S+", "Visit https://example.com")
print(result)Rok: 2025, Miesiąc: 03, Dzień: 06
{'year': '2025', 'month': '03', 'day': '06'}
['https://example.com']
Flagi modyfikują zachowanie wyrażeń regularnych:
| Flaga | Skrót | Działanie |
|---|---|---|
re.IGNORECASE |
re.I |
Ignoruje wielkość liter |
re.MULTILINE |
re.M |
^/$ dotyczą każdej linii |
re.DOTALL |
re.S |
. dopasowuje też \n |
re.VERBOSE |
re.X |
Pozwala na komentarze we wzorcu |
import re
# Łączenie flag operatorem |
text = "First Line\nSecond Line\nThird Line"
matches = re.findall(r"^\w+", text, re.MULTILINE | re.IGNORECASE)
print(matches)
# Flaga VERBOSE — czytelne wzorce z komentarzami
email_pattern = re.compile(r"""
^
[a-zA-Z0-9._%+-]+ # nazwa użytkownika
@ # symbol @
[a-zA-Z0-9.-]+ # nazwa domeny
\. # kropka
[a-zA-Z]{2,} # rozszerzenie domeny
$
""", re.VERBOSE)
print(email_pattern.fullmatch("user@example.com"))
print(email_pattern.fullmatch("invalid@@mail"))['First', 'Second', 'Third']
<re.Match object; span=(0, 16), match='user@example.com'>
None
Wiele metod akcesora .str w Pandas obsługuje wyrażenia regularne dzięki parametrowi regex=True. Pozwala to na wektorowe operacje regex na całych kolumnach.
import pandas as pd
data = pd.DataFrame({
'Text': [' Hello World ', 'Pandas Library43', ' Data Science ']
})
# Usunięcie znaków specjalnych (regex w str.replace)
data['Clean'] = data['Text'].str.strip().str.replace(r'[^\w\s]', '', regex=True)
print(data)
# Usunięcie liczb
data['NoDigits'] = data['Clean'].str.replace(r'\d+', '', regex=True)
print(data) Text Clean
0 Hello World Hello World
1 Pandas Library43 Pandas Library43
2 Data Science Data Science
Text Clean NoDigits
0 Hello World Hello World Hello World
1 Pandas Library43 Pandas Library43 Pandas Library
2 Data Science Data Science Data Science
str.contains() — filtrowanie z regexMetoda str.contains() pozwala filtrować wiersze na podstawie wzorca regex.
import pandas as pd
df = pd.DataFrame({
'email': ['jan@gmail.com', 'anna@wp.pl', 'piotr@firma.com', 'kasia@gmail.com', 'bad-email@@']
})
# Filtrowanie emaili z domeny gmail
gmail_users = df[df['email'].str.contains(r'@gmail\.com$', regex=True)]
print("Gmail:\n", gmail_users)
# Walidacja formatu email
df['valid'] = df['email'].str.contains(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', regex=True
)
print("\nWalidacja:\n", df)Gmail:
email
0 jan@gmail.com
3 kasia@gmail.com
Walidacja:
email valid
0 jan@gmail.com True
1 anna@wp.pl True
2 piotr@firma.com True
3 kasia@gmail.com True
4 bad-email@@ False
str.extract() — wyodrębnianie grupMetoda str.extract() wyodrębnia grupy przechwytujące z wyrażenia regularnego do osobnych kolumn.
import pandas as pd
df = pd.DataFrame({
'date_str': ['2025-03-06', '2024-12-25', '2023-01-15']
})
# Wyodrębnianie roku, miesiąca i dnia do osobnych kolumn
extracted = df['date_str'].str.extract(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})')
print(extracted) year month day
0 2025 03 06
1 2024 12 25
2 2023 01 15
str.extractall() — wszystkie dopasowaniaMetoda str.extractall() wyodrębnia wszystkie wystąpienia wzorca (nie tylko pierwsze).
import pandas as pd
df = pd.DataFrame({
'text': ['Ceny: 19.99 PLN i 5.50 PLN', 'Koszt: 120.00 PLN', 'Brak cen']
})
# Wyodrębnienie wszystkich kwot
amounts = df['text'].str.extractall(r'(\d+\.\d{2})')
print(amounts) 0
match
0 0 19.99
1 5.50
1 0 120.00
str.replace() z regex — zamiana wzorcówParametr regex=True w str.replace() umożliwia zamiany oparte na wyrażeniach regularnych.
import pandas as pd
df = pd.DataFrame({
'phone': ['123-456-789', '987 654 321', '(48) 555-123-456']
})
# Usunięcie wszystkiego oprócz cyfr
df['digits_only'] = df['phone'].str.replace(r'[^\d]', '', regex=True)
print(df) phone digits_only
0 123-456-789 123456789
1 987 654 321 987654321
2 (48) 555-123-456 48555123456
str.findall() — lista dopasowańMetoda str.findall() zwraca listę wszystkich dopasowań w każdym wierszu.
import pandas as pd
df = pd.DataFrame({
'text': ['Hashtags: #python #pandas #regex', 'No tags here', '#data is fun #science']
})
# Wyodrębnienie hashtagów
df['hashtags'] = df['text'].str.findall(r'#\w+')
print(df) text hashtags
0 Hashtags: #python #pandas #regex [#python, #pandas, #regex]
1 No tags here []
2 #data is fun #science [#data, #science]
Praca z polskimi znakami wymaga jawnego uwzględnienia ich we wzorcach lub użycia klas Unicode.
import pandas as pd
import re
df = pd.DataFrame({
'text': ['Zażółć gęślą jaźń', 'Hello World', 'Łódź jest piękna']
})
# Sprawdzenie, które wiersze zawierają polskie znaki
polish_pattern = r'[ąćęłńóśźżĄĆĘŁŃÓŚŹŻ]'
df['has_polish'] = df['text'].str.contains(polish_pattern, regex=True)
print(df) text has_polish
0 Zażółć gęślą jaźń True
1 Hello World False
2 Łódź jest piękna True
import pandas as pd
import re
# Zamiana polskich znaków na łacińskie
def zamien_polskie(text):
mapping = {
"ą": "a", "ć": "c", "ę": "e", "ł": "l", "ń": "n",
"ó": "o", "ś": "s", "ź": "z", "ż": "z",
"Ą": "A", "Ć": "C", "Ę": "E", "Ł": "L", "Ń": "N",
"Ó": "O", "Ś": "S", "Ź": "Z", "Ż": "Z"
}
pattern = r'[ąćęłńóśźżĄĆĘŁŃÓŚŹŻ]'
return re.sub(pattern, lambda m: mapping.get(m.group(), m.group()), text)
df = pd.DataFrame({
'text': ['Zażółć gęślą jaźń', 'Łódź', 'żółć']
})
df['ascii'] = df['text'].apply(zamien_polskie)
print(df) text ascii
0 Zażółć gęślą jaźń Zazolc gesla jazn
1 Łódź Lodz
2 żółć zolc