728x90
반응형

로마의 성당은 정말 화려하구나.

2023.04.15


 숙소에서 얼추 짐을 풀고, 돈과 카드를 잃어버리더라도 최소한의 피해만 발생하도록 여기저기 분할해 놓았다. 숙소는 듣던 데로 침대와 작은 책상, TV가 있었으나, 책상은 너무 작았고 대부분 TV가 자리를 차지하고 있었으며, 무엇보다도 천장이 너무 낮고 책상 바로 위의 천장은 지붕인지 비스듬하게 있어서 머리를 숙이고 다녀야 했다. 나름 1박에 18만 원으로 로마 치고는 싼 편이고 위치가 좋긴 하다만, 불편함은 어쩔 수 없었다.

 

 

 엘리베이터를 타고 0층에서 내렸는데, 호텔의 홀이 아닌 웬 생뚱맞은 좁은 공간이 나왔고, 옆의 하얀 건물에서 피아노와 바이올린, 첼로 등으로 추측되는 악기의 연주 소리가 들려왔다. 이런 공간과 엘리베이터가 연결되어 있는 것이 희한하긴 했지만, 조용한 분위기와 음악 소리가 제법 어울려 나쁘지 않았다.

 

 

 엘리베이터를 타고 1층으로 올라갔더니 다시 계단을 통해 홀로 가야 했고, 그 홀을 나가보니 정면에 보이는 차고 안에 작고 이쁜 올드 카가 한 대 보였다. 차에 대해 잘 모르는 나에게도 마음에 쏙 드는 공간이었다.

 

 

 아까 보다가 말았던 Largo di Torre Argentina로 돌아갔고, 처음으로 혼자 갔던 터키 여행 도중 잠깐 들렸던 불가리아의 풍경이 다시금 기억나는 경치들이 종종 보여왔다. 확실히 여기는 유럽이구나. 스케일은 비교할 게 못되긴 하지만.

 솔직히 큰돈 주고 여행 가는 것보다 집에 누워서 2주간 뒹굴 거리며 쉬는 것이 더 당기긴 했다만, 셀카를 찍을 때마다 사진 속의 내가 기분 좋은 미소를 짓고 있는 것을 볼 때, 싫지 않은 것은 확실했다.

 다만, 오자마자 비가 오다니, 원래 이렇게 비가 많이 오는 동네 인지 걱정이 조금 앞서긴 한다.

 

Sant'Andrea della Valle 성당의 정면과 돔

 Largo di Torre Argentina 주변을 크게 한 바퀴 돌고 판테온 방향으로 큰길을 건너가자 왠 큰 성당(Sant'Andrea della Valle) 하나가 보였다.

 적어도 3층짜리 건물은 되어 보이는 성당. 이 정도 크기의 성당이라면, 나름 이름이 알려진 성당이지 않을까?

 오늘은 단순히 산책하는 날로 정했으므로, 가능한 유명 유적지나 건축물에 들어가기보다 주변 구경만 하고자 했으나, 저렇게 눈에 띄면 가봐야지 않겠는가.

 출입이 가능한지, 위 층으로 올라갈 수 있는지 등등. 다양한 질문을 안고 성당으로 향했다.

 

Sant'Andrea della Valle 성당의 정면

 다행히 사람이 드나드는 것을 볼 때, 출입은 가능한 것으로 보였다.

 이탈리아는 유명한 가톨릭 국가이고, 엄청난 성당들이 사방에 널려있다고 하니, 건물 내부로 들어가는 첫 대상을 성당으로 해서 익숙해지는 것도 나쁘지 않겠지.

 

Sant'Andrea della Valle 성당의 내부

 성당의 내부는 지나치리만큼 화려했는데, 성당에 안 간 지 약 5년이 다 되어가는 내게도 신앙심을 불러일으키는 경이로움이었다. '이렇게까지 성당이 화려해도 되는가!'라는 의문이 안에서부터 튀어나올 정도로 화려했는데, 사방이 금칠에 대리석으로 여기저기 치장이 되어 있었다.

 

Sant'Andrea della Valle 성당의 천장화

 

Sant'Andrea della Valle 성당의 제단

 

Sant'Andrea della Valle 성당의 입구 방향

 

미켈란젤로의 피에타를 Gregorio De Rossi가 청동으로 만든 것

  입이 떡 벌어진 상태로 천장을 얼마나 쳐다보았는지, 사람들이 쳐다보기 쉬우라고 아래에 거울이 있긴 했으나, 줄이 제법 길어 줄을 서서 기다리느니 그냥 의자에 앉아서 천장을 보았다.

 성당이라 다행인 것은 별도의 입장료가 없었고, 실제 운영하는 성당인지 성수도 있었으며, 신자들을 위한 의자들이 많아 다리 아플 일은 없었다는 것이었다.

 그리고 어떤 남자 무리들이 의자에 앉아 소리를 내 묵주 기도를 한창 바치고 있었다.

 아, 건물은 단층이었다. 그저 천장이 엄청나게 높을 뿐. 올라가고 자시고 할 것도 없었고, 구석구석에 화려한 대리석 조각과 그림이 없는 곳이 없어, 이곳의 모습들을 찍은 사진을 전부 올린다면 이 성당의 사진만 10~20장이 올라갈 정도였다. 

 그렇게 됐다간 그냥 사진 자랑일 뿐, 여행기라곤 할 수 없지 않은가.

 

 심지어 난간마저도 검은 대리석과 붉은 대리석으로 만들어져 화려하지 않은 곳이 없었다. 

 그저 시골에서 올라온 촌놈처럼 성당 구석구석을 쏘다니며 감탄사를 연발하는 것 말고는 할 것이 없었는데, 이 성당에 대해 검색을 해봐도 손가락에 꼽을만한 대단한 성당이 아님에도 이토록 화려한 것을 보았을 때, 다른 성당들은 어느 정도 일지 가늠이 되질 않았다.

 무엇보다도 곳곳에 등이 달려있긴 했지만, 햇빛이 들어오니 성당의 윗부분이 따뜻한 금색으로 물드는 게, 건축 부분에서도 상당한 기술과 지식이 들어간 것이 확실했다.

 성당 구석구석에 비밀 공간의 입구 같은 곳도 제법 있었고, 해당 성당의 관련자라면 탐색하는 재미가 있을 듯하다.

 

 약 1시간에서 1시간 30분 정도 구경을 하고, 성당을 나와보니 비는 다 왔는지 해가 나고 있었다. 성당 안이 햇빛으로 금빛으로 물들 때부터 해가 나고 있었나 보다.

 내일 아침에 보고자 한 판테온 방향으로 발길을 옮겼는데, 차 한 대가 굳이! 자신이 가던 차로의 옆차로로 넘어와 물 웅덩이를 대차게 밟고 내게 물을 끼얹었다.

 아니, 차가 막히는 상황도 아니었고, 쭉 직진을 해도 되는 상태에서 굳이 왜 내 옆 도로까지 들어와서 물을 튀기고 가는 것인지, 그나마 다행히 바지와 외투가 조금 젖고 상당량의 물은 우산으로 막아냈다만, 길에 고여 회색빛이 도는 지저분해 보이는 물이 튀니 썩 기분이 좋지 않았다.

 혼자 하는 여행에선 가능한 한 빨리 기분 나쁜 일을 잊고 나아가는 것이 필요하므로, 대충 수습하고 다시 나아갔다.  

 

Basilica of St Eustace의 입구

 

 판테온으로 향하던 길에 성당(Basilica of St Eustace)이 하나 더 있었고, 혹시 이 성당도 앞서 본 성당(Sant'Andrea della Valle)만큼 화려할까 싶어 한 번 들어가 봤다.

Basilica of St Eustace의 내부
Basilica of St Eustace의 도금된 난간과 오르관

 모든 성당이 Sant'Andrea della Valle처럼 화려하기만 한 것은 아닌지, 이 성당은 비교적 소박했고, 일반 신자들이 많이 오는 성당처럼 보였다.

 입구에서부터 구걸을 하는 분이 한 분 계셨고, 벽화가 없는 것은 아니었으나, 이전 성당만큼 과하다 싶은 수준은 아니었다. 다만, 이 성당은 무려 8세기에 지어진 녀석으로 1582년 지어지기 시작해 1650년 완공된 Sant'Andrea della Valle에 비해 역사적인 가치가 더 많다면 많았지, 부족함은 없는 녀석이다.

 지나가다 설렁 들린 곳마다 영문 위키 백과에 없는 녀석이 없고, 내용이 상당하니 로마가 대단하긴 대단한가 보다.

 

판테온(Pantheon)의 옆면

 

판테온(Pantheon)의 앞면

 

판테온 앞의 오벨리스크와 분수(Obelisco del Pantheon)

 

판테온 앞의 오벨리스크와 분수(Obelisco del Pantheon)

 

 판테온은 다음날에 자세히 다룰 예정이므로, 주변만 설렁 보고 지나갔으나, 확실히 사람이 많다. 

 처음 로마에서 길을 걸으면서, 들렸던 곳들이 전부 역사적인 가치나 예술적인 가치가 어느 하나 빠지지 않는 상당한 곳들이었으나, 듣던 것보다 사람이 별로 없길래 비수기인가 했더니, 죄다 여기 있었나 보다.

 입장해보고 싶었으나, 구체적인 입장 방법이 적혀있거나 안내를 하는 사람으로 보이는 이도 없었고, 판테온 내부에는 사람이 있었으나, 입장을 막는 줄이 사방에 쳐져 있어서 밖에서만 구경할 수 있었다.

 오늘의 목표는 판테온의 외부 탐색까지 만 이었으므로, 자세한 건 내일 아침 일찍부터 가서 보면 알게 되지 않을까 싶다.

 

 

728x90
반응형
728x90
반응형

비 오는 로마에서의 산책

2023.04.15

 


 두바이에서의 날씨는 정말 좋았다만, 로마에서의 첫날은 우중충하고 비가 오는 날이었다. 그리 많이 오는 비는 아니었다만, 활주로가 조금 젖어있었고, 비행기에서부터 창문에 빗방울이 튀기 시작하고 있었다.

 

로마 FCO 공항

 

 로마 FCO 공항에서도 한국에서처럼 자동 입국 심사가 됐고, 여권을 찍고 얼굴 사진을 찍고 바로 패스가 됐다. 유럽의 입국 심사는 꽤 느긋하다고 들었는데, 자동 입국 심사 때문인지 도장을 찍어주는 직원은 정신없이 도장을 찍고 있었고, 예상보다 너무 빠르게 이탈리아에 들어오는 데 성공했다.

 

 

 자, 유럽에서 사용 가능한 USIM 칩 교체는 비행기 안에서 끝냈고, 이제 숙소까지 가는 길만 남았는데, 이 과정이 그다지 만만치는 않다. 가장 쉬운 것은 공항에서 숙소까지 택시를 타는 것이겠지만, 이탈리아의 택시비는 한국과는 비교가 안될 정도로 비쌌고, 당장 알고 있던 이동 방법은 셔틀버스였지만, 블로그 등에서 찾아본 후기에선 짐을 싣고 내리는 과정에서 분실의 위험이 있으니 조심하라는 글이 큼지막하게 쓰여있었던 것을 볼 때, 만만치 않을 듯했다.

 

 해외여행에서 가장 든든한 친구인 구글 맵은 지하철인지 기차인지 정확히는 모르겠는 무언가를 타라고 하였고, 한치의 망설임 없이 매표기에서 표를 끊었는데, 아... 너무 가까운 시간대의 표를 끊는 바람에 숨 돌릴 틈 없이 기차역으로 뛰어가게 됐다. 늦을까 봐 뛰어가는 것은 그다지 좋아하지 않는 일인데, 기차 출발까지 15분이 채 남지 않은 상황이라 앞 뒤로 배낭을 메고 뛰었다.

 

 기차역에 들어서서 직원에게 "It's right!?"을 연신 물어보며, 늦지 않게 기차에 타는 것에 성공을 하였고, 나 이외의 영국인으로 보이는 여행객 무리와 점잖은 이탈리아 할아버지와 친화력 높아 보이는 할머니 등이 열차에 올랐다. 이 할머니가 내게 말을 건 것은 아니다만, 이 영국인 무리들과 내가 기차에서 내릴 때까지 쉬지 않고 수다를 떨어주셔서, 심심치 않게 기차를 타고 갈 수 있었다.

 

 

 어찌나 서둘렀는지 공항에서 사진 한 장 찍지 않고, 기차 사진도 찍질 못했다. 확실히 외국에 오긴 한 건지 아파트나 주변 식물 모든 것들이 바뀌었다. 다만, 기차 안에서 방심하지 못하겠는 것이 소매치기 같은 게 문제가 아니고, 열차에서 지금 역이 어딘지 방송이 나오지를 않으니, 잘못된 역에서 내릴까 봐 구글 지도만 쭉 보고 있어야 했다.

 

Roma Trastevere에서의 풍경

 

 Roma Trastevere에 내리니 많지는 않지만 추적추적 비가 내리고 있었고, 여기서부터 버스를 타고 숙소로 이동해야 했다. 로마에서는 표를 담배 가게나 길거리의 작은 부스 등에서 판매한다고 하여, 구글 지도가 알려주는 방향으로 이동을 하며, Tabacco 또는 Tobacco 라 쓰인 가게를 찾아다녔다.

 

우중충 하긴 했지만, 제법 마음에 들었던 풍경이었다.

 

 확실히 유럽에서는 우산을 많이 쓰질 않아서 그런지, 비가 제법 날리는 데도 그냥 맞고 돌아다니는 사람이 제법 되었고, 위 사진의 맨 오른쪽 건물을 살짝 돌자, 담배 가게가 보였다. 우리나라의 편의점과 비슷하게 생겼으나 구멍가게처럼 크기는 매우 좁았고, 덩치 큰 이탈리아 형씨가 가게를 보고 있었다.

 

 1.5유로에 버스 티켓 한 장을 끊으러 들어갔고, 주말이라 그런 것인지 한국처럼 복권을 사러 온 사람이 제법 되었다. 버스 티켓 한 장을 사서 바로 버스를 타로 이동했는데, 습기 가득한 이 가게의 사진을 찍지 않았던 것이 이제 와 생각하니 조금 아쉬웠다.

 

우중충한 로마의 길거리

 

 버스를 타러 가는 중에 경찰 둘이 이 비 오는 날씨에 인도 한 복판에 대짜로 누워있는 양반에게 화를 내며 일으키려는 모습을 보았는데, 이탈리아에서 약 7~8년 전부터 소매치기의 왕국 로마라는 오명을 벗기 위해 경찰과 군인을 상당수 투입했다더니, 가장 쉬운 먹잇감 중 하나인 1인 자유 여행자로선 나쁘진 않은 일이긴 하다.

 

 버스를 탄 것까진 좋은데, 기차에서처럼 알람이 울리질 않는다. 그나마 기차에선 모니터로 글을 띄워주기라도 했는데, 버스엔 그런 것도 없어서, 이거 USIM 칩을 한국에서 사 오길 정말 잘했다. 빗발이 제법 거세지는지 창 밖 너머로 우산을 든 사람과 우의를 입은 사람들이 점점 늘기 시작했다.

 

로마의 캄피톨리오(Campidoglio) 광장의 계단과 제우스의 쌍둥이 아들 카스토르와 폴룩스의 조각상

 

 버스에서 내리자마자 아주 멋있고 큰 이름 모를 조각상 둘이 반겨줬는데, '이 것이 로마인가!'라는 감정을 불러일으키는 데는 충분했다. 버스 하나에서 내렸다고 이렇게 있어 보이는 조각상이 보이다니 시작이 나쁘지 않은 듯하다. 

 

 구경을 좀 하고 싶긴 했는데, 빗발이 점점 늘고 있었고, 우산이 가방 깊숙이 어딘가에 있었기 때문에 그 비를 모조리 맞고 있는 중에 그럴 여유를 부릴 수는 없었다. 

 

산타 마리아 인 아라코엘리 성당과 조국의 제단(옆 모습)

 

 눈길을 돌리니 거기에도 들어가 봐야겠다는 생각이 드는 규모의 건물이 더 있었는데, 비를 맞고 있는 상황임에도 사진 찍는 것을 멈출 수가 없는 건물들이었다.

 

조국의 제단(옆 모습)

 

 순백의 대리석으로 지어진 이름 모를 거대한 건축물이 조금 걷고 다시 돌아서 사진을 찍고, 조금 걷고 다시 사진을 찍는 행동을 반복시켜 주었고, 어느 정도 만족한 후에야 구글 지도를 보며, 숙소로 다시 이동을 할 수 있었다.

 

비가 오는 중인 로마 길거리

 

 비가 한창 오는 중임에도 불구하고, 어디에 카메라를 들이밀어도 마음에 드는 사진을 한 장씩 건질 수 있었는데, 오래된 건축물이 많아서인지 보수 중인 곳이 제법 많았다. 그리고 길이 상당히 좁고, 울퉁불퉁한 돌로 길이 만들어져 있어서 트렁크 대신 배낭만 2개를 메고 온 선택이 틀리지 않았다는 생각이 들게 해 주었다.

 

 

 얼마 지나지 않아 예약한 숙소인 오시아눔 궁전(Historic Hosianum Palace)에 도착하였다. 중년의 이탈리아 남성 분의 따뜻한 환대를 받으며 Check-in을 하였고, 그분은 로마의 지도가 필요하지 않냐 물으며 하나를 꺼내주었다. 곧이어 그 지도의 중요 지점들에 펜으로 줄을 쭉쭉 그어, 어떤 동선으로 다니는 것이 가장 효율적이고, 바가지를 씌우지 않는 괜찮은 레스토랑들이 어디 있는지 알려주었다.

 

 숙소는 판테온과 걸어서 10분 거리에 있었고, 판테온을 기준으로 위쪽은 엄청 비싸니 거기서는 커피 한 잔 사 먹지 말라며 당부를 주었다. 그리고 밥은 좌측 하단의 동그라미 쪽에 가격도 훨씬 저렴하고 맛도 좋은 레스토랑이 많으니 무조건 거기서 밥을 먹으라고 알려주었다.

 

 숙소 주인 분의 따뜻한 환대와 친절한 안내를 받고, 4층에 있는 방으로 가서 짐을 풀었다. 우산을 꺼내고, 슬리퍼를 신으려고 숙소 구석구석을 찾아봤으나, 한국과 달리 침대에서도 신발을 신는다고 하는 유럽인들이어서인지 슬리퍼가 어디에도 없었다.

 

 한국처럼 숙소에 기본적으로 슬리퍼를 비치해 둘 줄 알았더니, 일단 슬리퍼도 살 겸 해서 가볍게 산책을 하러 나가봤다. 비행기에서 가이드북을 열심히 읽어보긴 했으나, 로마 쪽에 워낙 내용이 많아서 기억나는 게 하나도 없었기도 했고, 생각 없이 사전 답사를 하는 것도 나쁘지 않지 않은가.

 

비오는 로마 시내, 어디에든 유적지의 흔적이 보인다.

 

로마에서 처음으로 본 성당(Chiesa di San Giuliano dei Fiamminghi)의 돔

 

Largo di Torre Argentina(1)

 

 숙소 밖으로 조금 걸어 나오자 큰 길이 나왔고, 숙소의 직원분이 그려준 길을 따라가니 성당의 큰 돔과 넓은 유적지가 보였다. 비와 오랜 비행 탓에 춥고 피곤하긴 했다만, 제법 마음이 설렜다.

 

 Largo di Torre Argentina*는 출입은 불가능했으나, 내부를 보는 것은 얼마든지 가능했고, 당연히 무료였다. 

 

Largo di Torre Argentina(2)

 

Largo di Torre Argentina(3)


Largo di Torre Argentina*: 이탈리아 로마에 있는 광장 중 하나로, 로마 공화당 사원과 폼페이우스 극장 유적으로 이루어져 있음. 폼페이우스 극장 유적 입구에 있었던 Pompey Curia에서 율리우스 카이사르(Julius Caesar)가 암살당했다고 함. 2019년 로마 시장은 이곳의 주변에 산책로를 설치해 일반 대중이 볼 수 있게 하였으며, 역사적인 가치 외에도 고양이 대피소가 존재하는 것으로 유명함.


 

 Largo di Torre Argentina의 옆에 있던 조그만 구멍가게에서 6유로짜리 쪼리를 하나 산 후, 숙소로 다시 돌아가 잠깐 정비를 한 후 산책을 이어 가기로 마음먹고 숙소로 발길을 돌렸다.

 

 

728x90
반응형
728x90
반응형

아침에 창가 자리에 앉으면 눈이 너무 부셔서 창문을 열어놓을 수 없다.

2023.04.15


 운 좋게, 창가 자리에 앉을 수 있었다. 요즘은 자리 위치까지 특별 금액이 붙고, 온라인 Check-in을 하기 때문에 공항에 일찍 가서 좋은 자리를 선점하는 로맨틱한 문화가 사라져 버렸다. 운 좋게 발을 뻗을 수 있는 자리 나 창가 자리에 앉게 되면 그날 기분이 제법 좋았었는데, 비행기가 출발하기 며칠 전 온라인 Check-in으로 운 좋게 비행기 자리를 잘 잡는 것밖에 방법이 없다는 것이 아쉬울 따름이다.

 

 

 두바이 공항에서 유럽으로 가는 비행기에서 창가 자리에 앉으면 인터넷에서 한 번쯤 보았을법한 인공 해변과 잠시간의 바다, 넓은 황토색의 평지가 보인다. 

 그렇게 개성이 강하지는 않지만, 구름이 얼마 끼지 않은 덕에 넓은 바다와 광활한 땅을 볼 수 있었고, 이륙한 지 얼마 되지 않아 약 한 시간 반 정도 기절한 듯 잠에 들었다.

 

 

 한 숨 자고 일어나니, 승무원분들이 따뜻한 미소를 지으며, 가벼운 아침 식사로 치즈 에그 샌드위치와 딸기 패스츄리를 내왔다. 원래 둘 중 하나를 선택하는 것이지만, 기내식 때문에 카페모카 한 잔만 마신 나로서는 출출하기도 했고, 앞서 다른 승객들이 두 개를 주문해서 받는 것을 보고 당당히 두 개를 주문했다.

 우리 쪽 줄을 담당한 승무원분은 키가 크고 이목구비가 시원시원하게 생겨 웃는 모습이 이쁜 여성분이었는데, 이코노미라 그런 것도 있겠지만, 승무원은 보통 일은 아닌 거 같다. 이 좁은 공간에 앉아 있는 것만도 버거운데 승객의 각종 요청과 음식, 음료 등을 서빙하는 일이라니 아무나 하는 일은 아니로다 싶다.

 스크램블 애그가 들어있는 샌드위치는 적당히 따끈하고, 안 속에 스크램블 애그뿐만 아닌 치즈도 들어 있어서 하나 더 먹고 싶을 정도로 맛있었고, 커피도 상당히 맛있었다. 한국에서 즐겨 먹던 아메리카노와 다른 블랙커피는 확실히 진하니 향도 좋긴 한데, 커피를 몇 번째 마시는 건지 모르겠다. 반면에 딸기 패스츄리는 억지로 먹었다 싶을 정도로 맛이 없었는데, 딸기잼의 향이 진하지도 않았고, 그다지 달지도 않아 어렸을 때, 어머니께서 건강에 좋다며 만들어주셨던 산딸기 잼이 생각나는 맛이었다.

 아침을 먹고 나니 음료 카트가 한 번 더 왔다 갔고, 아까 옆자리에 앉아 있던 백인 친구들처럼 커피에 우유를 넣어먹어 봤다. 조그만 캡슐에 우유가 들어 있었고, 우유만 넣지 않고 설탕까지 넣으니 상당히 맛이 좋았다.

 

이제 기내식에 김치는 존재하지 않는다.

 

 눈이 너무 부셔서 창문을 닫고 출국 전 다운로드 해놓은 넷플릭스를 잠시 보고 있자니, 다음 기내식 시간이 되었다. 비행시간이 그리 길지 않은데 얼마 되지 않아, 다른 이가 끼니를 계속 가져다주는 것은 아무래도 적응이 되질 않는다. 한국에서 버터와 설탕 등을 빵에 잔뜩 넣어 식사가 아닌 간식용으로 만들어진 빵에 익숙해있던 내게 빵에 버터를 발라 먹는 것은 이해하기 어려운 행동이었다만, 식사용으로 만들어진 빵에 버터를 발라 먹는 것은 제법 별미였다. 

 당분간은 쌀밥이 아닌 빵이 주식이 되겠구나, 첫 여행 때도 빵이 제법 잘 맞았었는데, 이탈리아는 어떤 맛있는 빵을 가지고 있을지 궁금증이 돋았다.

 

알프스이지 않을까?

 

  비행시간이 약 1시간쯤 남았을 때, 창밖으로 넓은 범위로 산과 눈이 보였고, 시간을 봤을 때 이탈리아 북부의 알프스 산맥 위를 날고 있는 듯했다. 온라인 Check-in으로 창가 자리에 앉은 것이 상당히 만족스러운 순간이었다. 이때부터 '아, 내가 왔구나'라는 생각이 딱 들었다.

 

 

 알프스로 추정되는 눈이 쌓인 산을 지나 중동과 다른 녹음으로 우거진 땅과 넓은 바다(또는 호수?)가 보였고, 곧이어 넓은 목초지와 해변이 보이기 시작했고, 비행기가 하강하기 시작했다. 로마 FCO 공항은 해안가에 가깝다고 하니, 기나긴 비행 끝에 로마 FCO 공항에 다 온 듯했다.

 

 

 창밖으로 보이는 그 풍경이 제법 아름다워 수 없이 사진을 찍어댔고, 바다는 사나웠으며, 아담한 붉은빛에 가까운 건물들과 한국에서 보기 어려운 초원 위 둥글둥글한 나무들이 보였다. 이전에 같은 지중해권 국가인 터키에서 봤던 나무들과 비슷한 모양새였다.

 환승 대기 포함 약 19시간의 비행 끝에 드디어 이탈리아에 도착했구나...

 

 

728x90
반응형
728x90
반응형

8년 만에 온 두바이 공항

2023.04.15


 두바이 공항에 도착해 뻐근한 몸을 스트레칭하고, 환승해야 할 비행기의 Gate는 어딘지 찾으러 가려는 중에 아잔*이 울려 퍼졌다. 난 천주교 신자이긴 하다만, 아잔은 항상 듣기 좋다. 터키에서 64일간 여행을 했을 당시, 매일 울려 퍼지던 아잔을 생각지도 않은 두바이 공항에서 들으니 감회가 새롭다.


아잔*: Adhan, 이슬람에서 하루 5번 바치는 기도 시간을 알려주는 기도 소리.


 아잔 소리를 한껏 음미한 뒤, 여권과 비행기 표 사진을 찍지 않았다는 것이 떠올라 냉큼 사진부터 찍었다. 내가 해외에 나갔다는 상징성이 가장 큰 이미지 중 하나이므로, 다른 건 잊어버려도 이것만큼은 찍어야지 않겠는가.

국민 공통의 암묵적 합의 사항인 출국 시 여권과 보딩 패스 사진이다.

 

 두바이 공항이라... 처음 해외여행을 갔을 때, 최종 도착지를 제외하고 나머지를 거의 동일한 경로로 하여 터키로 여행을 갔었는데, 뭔 깡인지 처음 여행을 가면서 혼자 떠났어서 공항에서 멘붕을 했던 적이 있었다.

 다름 아니라, 비행기를 환승하는 경우, 환승할 비행기의 탑승지인 Gate가 공란으로 적혀 있는 경우가 있는데, 이는 아직 비행기가 들어올 Gate 번호가 정해지지 않아서 그런 것이지만, 그것을 모르는 여행 초짜에게 있어서는 국제 미아가 될만한 사유였다.

 그 당시 공항에서 와이파이를 쓸 생각도 하지 못하고, 영어도 한마디 하질 못해 식은땀범벅이 가 돼 공항을 1~2시간 정도 방황했었는데, 이번엔 여유롭게 비행기 시간대가 나오는 게시판으로 가서 비행기 Gate가 언제 나오는지를 확인하고 움직였다. 그때, 국제 전화로 해외여행을 가본 적이 있던 친척 형에게 물어물어 해결을 했었는데, 두바이 공항에 오니 그때의 추억이 새록새록 솟아오르는 군.

 당시엔 영어를 전혀 알아듣지도 말하지도 못해서, 공항의 직원이 도와줬을 때, 더 멘붕을 했었는데, 이 번엔 능숙하게 공항 직원들의 가이드를 받아가며 내가 가야 할 위치를 수월하게 찾아갔다.

 

 

 세계 최대 규모의 면세점을 가지고 있는 두바이 공항은 24시간 면세점을 운영하므로, 원하는 것이 있으면 언제든 살 수 있는 곳인데, 덩치에 비해 어딜 가나 비슷비슷해서 그다지 볼거리가 많지만은 않다. 8년 전 왔을 때, 이 공항에서 9시간이나 다음 비행기를 기다리며 공항을 구경했기 때문에 반가움 말고는 그다지 구경하고 싶지는 않았다.

 

아랍에미레이트의 특산품 대추 야자 열매

 다만, 티르키쉬 딜라이트인 로쿰이 있는 코너에선 발이 딱! 붙어서 떨어지질 않았는데, 이건 제법 맛있고 국내에서 구하기도 어렵단 말이지... 지금 사면 짐이니까, 국내로 돌아갈 때 사던가 해야겠다. 아랍에미레이트의 특산품인 대추야자열매도 있었는데, 이건 너무 달아서 많이 먹지도 못하고 체질이 맞는 과일이 많지 않은 편이라 나중에 돌아갈 때 살지 말지 고민을 해봐야겠다.

 

두바이 공항에서 잠깐 쉬어간 카페

 

 환승까지 4시간 대기를 해야 했기에 한 카페에 가서 25 두바이 달러(한화 8,875원)에 달하는 카페 모카 한 잔을 홀짝이며 여유를 부렸다. 계산하는 과정에서 여행 가기 전에 바꾼 핸드폰을 꺼내 환율을 확인하고 있으니, 카페 직원이 새로 나온 기종인 것을 눈치채고, 구경해도 되는지 물어봤다.

 이전에 쓰던 핸드폰을 4년 정도 써서, 배터리가 너무 빨리 달기도 했고, 이번에 나온 핸드폰 기종(S23 울트라!)의 카메라가 제법 좋다는 이슈가 있어서, 카메라도 안 들고 갈 겸 핸드폰을 바꿨었는데, 덕분에 수다 떨 일이 생겨서 좋았다. 휴대폰 대리점의 직원이 된 기분으로 핸드폰의 기능을 소개해주고(사실 카메라가 엄청 확대가 잘되고 후보정이 되는 거 말곤 모른다), 직원들이 잠시 카메라를 가지고 노는 것을 지켜보았다. 한국이나 외국이나 새로운 전자기기가 나오면 관심이 쏠리는 방식은 비슷하구나.

 커피가 나오기 전, 기념 삼아 사진을 한 장 찍으려 하니 직원들이 포즈도 취해줬다.

 

두바이 공항 카페에서의 커피 한 잔

 8,800원짜리 두바이의 값비싼 커피를 한 잔 하며, 핸드폰 충전과 일기를 쓰는 시간을 가졌다. 이전 첫 여행에서 기내식을 먹을 땐, 좁은 공간에 꼼짝 못 하고 주는 음식을 계속 받아먹으니 가축이 된 기분을 느꼈었는데, 이젠 나도 성장을 한 것인지, 아니면 사회가 어떻게 돌아가는지에 익숙해져서인지 제법 먹을만했었고, 속도 나쁘지 않았다.

 그때는 자다 깨다를 반복하며 먹어서인지 속도 영 더부룩하고 기분도 썩 유쾌하진 않았었는데, 이번엔 아주 깔끔했다. 다만, 불편한 잠자리는 확실히 느꼈고 비즈니스 클래스를 부담 없이 탈 수 있는 재력을 길러보리라 마음먹게 되었다.

 조식 땐 난기류가 제법 심해 음식을 엎을까 숙이고 밥을 먹었었는데, 그 덜컹거리는 와중에도 미소를 잃지 않고 서빙을 하는 승무원들의 프로 정신에 감탄할 다름이다.

 

두바이 공항의 개성 중 하나인 대형 폭포와 엘레베이터

 

  비행기 탑승 시간이 2시간 정도 남아, 게이트를 향해 이동했다. 두바이 공항에는 큰 인공 폭포가 있고, 엘리베이터를 이용해 층간 이동 후, 열차를 타고 게이트를 이동할 수 있는데, 8년 전 경험이지만 여전히 기억이 나서 수월하게 이동을 했다.

 

 

 이번에 비행기를 환승하는 곳이 8년 전 비행기를 환승했던 곳과 길이 거의 동일해 그때의 풍경을 다시 즐길 수 있었다. 8년이나 됐는데, 맥도널드의 위치는 그대로였다. 로마행 비행기에서 기내식을 1번과 간식 1번을 먹어야 하는 것만 아니었다면, 아랍에미레이트의 시그니쳐 메뉴를 먹어보겠는데, 전혀 배가 고프질 않아 넘겼다.

 공항 식당은 어딜 가나 바가지기 때문에 어지간하면 사 먹지 않는 것이 낫다만, 맥도널드는 믿을만하지 않은가. 8년 전에 맥도널드 앞 피자 가게에서 2조각에 피자 한 판 가격을 주고 먹어본 적이 있어서, 이 맥도널드는 잊으래야 잊을 수가 없다. 그리고 그 피자 가게는 문을 닫고 대형 사탕 가게가 그 자리에 들어와 있었다.

 

 

 잠깐의 두바이를 뒤로 하고, 이제 로마 FCO로 향하는 비행기에 탑승을 했다.

 이걸로 두바이엔 3번째(한국에서 터키로 향할 때, 터키에서 한국으로 향할 때, 한국에서 이탈리아로 향할 때) 들리는 것인데, 언젠간 공항 안에서만 돌아다니지 않고 두바이 시내를 활보하는 날이 오지 않을까 싶다.

 

 

728x90
반응형
728x90
반응형

8년 만에 혼자 온 인천 공항

2023.04.14


 나 자신이 좋아하는 것, 나 자신이 싫어하는 것, 내가 당장 원하는 것이 무엇인지, 온전히 나에게만 시간을 투자하며 알아 가는 시간을 갖는 것은 우리 삶 속에서 그다지 쉬운 일만은 아니다.

 

 나름 열심히 가꿔왔던 이 블로그도 업무에 치여서 포스팅을 하지 않은지 2년 가까이 됐고, 대형 프로젝트를 하나 했더니 몸도 마음도 망가져서, 너무 많이 조여 언제 끊어질지 모르는 기타 줄처럼 팽팽해진 상태가 되어 마음에 여유를 잃은 지 제법 되었다.

 

 우리는 바삐 돌아가는 세상 속에서 당장 내일의 경쟁에 밀려, 트렌드에 뒤쳐져 뒷방 늙은이 취급을 당하지 않기 위해, 쉼 없이 뛰어가야만 하고, 퇴근을 하거나, 주말이 되거나, 마음을 터놓을 수 있는 이들과 함께 있는 순간마저도, 정체 모를 불안감이 마음속 어딘가에 묵직하게 자리 잡고 자신의 존재감을 드러내는 덕에 자신을 위해 온전히 시간을 쏟지 못하며 하루하루 어정쩡하게 살아간다.

 

 1년 가까이 나를 괴롭혀왔던 프로젝트도 어느 정도 마무리 되어 고생 대가로 의미로 2주간의 휴가를 받았고, 내 인생의 가장 큰 방황기였던 26살에 처음으로 갔던 터키로의 자유 여행에서 인생의 전환점을 얻었던 기억이 있었기에, 8년 만에 또 혼자 여행을 가보기로 마음먹었다. 4월 14일 밤 11시 55분 비행기를 타고 두바이로 가서 로마로 가능 비행기로 환승한 후, 이탈리아에서 15박 16일을 보내고 4월 30일 13시 25분 비행기를 타고 한국으로 돌아오는 오랜만에 제법 긴 여행이다. 터키 여행 이후로 가족 여행 말고 딱히 여행을 다닌 적이 없었기에 설렘 반 걱정 반으로 퇴근 후 짐을 들고, 인천 공항행 버스에 몸을 싣었다.

 

 

 공항에 도착하자마자 체크인 수속을 마쳤고, 요즘은 국내에서 USIM 칩을 미리 살 수 있다길래 인천공항 서점에서 19,900원에 한 달에 10GB짜리를 하나 샀다. 현지에서 산다면 조금 더 쌀 수 있겠지만, 스마트폰을 쓸 수 있고 없고에 따라 여행 난이도가 크게 달라지기 때문에 1분이라도 이익을 볼 수 있다면 사는 게 맞지 않나 싶다.

 

 여행을 그리 많이 나가본 것은 아니지만, 지각을 자주 하는 습관 때문에 공항에 항시 3~4시간 일찍 가있다 보니, 인천 공항의 어지간한 건 다 봐놨기 때문에 바로 Gate로 향했다. Gate는 한산했고 몇몇 이들은 충전기를 꽂을 수 있는 자리에 모여 전자기기를 충전하며 각자의 방식대로 편하게 자리를 잡고 앉거나 누워 스마트폰을 보고 있었다.

 

 8년 전에 혼자 터키 여행을 갔을 때도 이번에 타는 에미레이트 항공을 타고 갔었는데, 당시엔 체중이 지금보다 약 10kg 이상 덜 나갔기 때문에 몸이 제법 가벼웠었는데, 이번 여행에선 배가 제법 나와 몸을 움직이는 것이 힘들다. 이번 여행이 끝나고 돌아가면 다이어트를 시작하긴 해야겠다.

 

이번에 타고 갈 에미레이트 항공의 에어버스

 

 매번 밤 늦게나 새벽 비행기를 타서인지 인천 공항에서의 경치는 항상 깜깜했다. 비행기에서 잘 자지 못하는 편인데 요즘 피로가 많이 쌓여서인지 머리만 대면 자므로, 이전에 비해 걱정이 덜 하다. 두바이로 가는 비행시간은 약 11시간, 기내식 2번에 시차 때문에 출발부터 도착까지 쭉 밤이다. 여행 출발 전 매 여행 때마다 그래왔듯, 부모님과 친구들에게 안부 인사를 남겼고, 잠시 후 비행기에 올랐다.

 

 

 기대대로 비행기가 이륙한 지 얼마 되지 않아 곤히 잠들었고, 실컷 잔덕에 준비해 간 넷플릭스는 거의 보지를 못했다. 귀신 같이 기내식 때에 눈이 떠졌고, 이번 기내식은 제법 나쁘지 않았다. 특히 이전에 터키 여행을 갈 땐, 영어를 잘 알아듣지 못해 죽이 아닌 스크램블 에그를 먹어서 후회했던 적이 있었는데, 이번엔 능숙히 죽을 시켰고, 저녁을 안 먹고 출발한 덕인지 두 끼 모두 간식을 조금 남기고 모두 해치웠다.

 

 

 에미레이트 항공은 한국에서 출발하거나 한국으로 가는 비행기는 맛이 부족하긴 해도 나름 김치가 나오기 때문에 심심하지 않게 해치울 수 있었다. 이전에 터키에 갈 땐, 이 김치의 소중함을 몰라 손도 대지 않았는데, 해외에 가면 다른 건 몰라도 김치 생각은 날 수 있으므로 먹을 수 있을 때 먹도록 하자.

728x90
반응형
728x90
반응형

이전 포스트까지 정규 표현식이 무엇인지, Python에서 정규 표현식을 어떻게 탐색하는지에 대해 알아보았다. 이번 포스트에서는 가장 중요한 "메타 문자로 정규 표현식 패턴 만들기"에 대해 알아보도록 하겠다.

 

 

 

1. 메타 문자로 정규 표현식 패턴 만들기

  • 메타 문자로 정규 표현식 만들기는 크게 3가지 방법으로 돌아간다.
  1. 패턴 문자열 생성: 찾고자 하는 패턴 문자열(문자 클래스)을 생성한다.
  2. 패턴 문자의 반복 지정: 패턴 문자열이 몇 번 반복되는지 지정하기
  3. 패턴 문자의 위치 지정: 패턴 문자열로 시작하거나 패턴 문자열로 끝나는지 지정한다.
  • 위 3개 과정은 다 함께 진행될 수도 있고, 하나만 진행될 수도 있다. 이 외에도 문자의 패턴들을 그룹으로 만들거나 |(or)를 이용하여, 원하는 패턴이 존재하는 경우를 선택하게 할 수도 있다.
  • 각 부분에 해당하는 메타 문자는 이전 포스트인 <Python-기초:3.0. 정규 표현식(1) - 소개: 정규 표현식과 메타 문자>를 보면 깔끔하게 표로 정리해놨으니, 이를 참고하기 바란다.

 

 

 

 

2. 패턴 문자열 생성 하기

2.1. 문자 클래스 [ ]

  • 문자 클래스(character class)라고 하며, [ ] 안에는 1개 이상의 문자를 넣을 수 있고, 그 문자 중 하나라도 일치한다면 일치하는 패턴으로 판단한다.
# [abc]라는 정규 표현식 패턴 생성
>>> pattern = "[abc]"
>>> regex_pattern = re.compile(pattern)

>>> Text_cat = "cat"
>>> print(regex_pattern.findall(Text_cat))
<re.Match object; span=(0, 1), match='c'>


>>> print(regex_pattern.findall(Text_cat))
['c', 'a']
  • "[abc]"는 3개의 문자가 들어 있는 것으로 보이지만, [ ]는 문자 하나로 인식한다.
  • cat은 "[abc]" 내부의 c와 일치하므로, match 되는 값인 'c'를 반환하였다.
  • findall() 메서드로 탐색 시, 일치하는 패턴인 ["c", "a"]를 하나하나 반환하였다.

 

 

2.2. 문자 클래스 [ ]와 범위 특수 문자 "-"

  • [ ] 안에 들어간 특수문자는 메타 문자가 아닌 원본 문자의 의미로 사용된다.
  • 그러나, "-"와 "^"는 별개의 의미를 갖는다.
  • "-"는 문자 클래스 내에서 범위를 의미한다.
>>> pattern = "[0-9]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I have 10 Cats"
>>> print(regex_pattern.findall(Text_data))
['1', '0']
  • "[0-9]"는 0에서 9까지의 숫자를 의미한다.
>>> pattern = "[a-z]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I have 10 Cats"
>>> print(regex_pattern.findall(Text_data))
['h', 'a', 'v', 'e', 'a', 't', 's']
  • "[a-z]"는 a부터 z까지의 영문자, 즉 소문자를 의미한다.
>>> pattern = "[A-Z]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I have 10 Cats"
>>> print(regex_pattern.findall(Text_data))
['I', 'C']
  • [A-Z]는 A부터 Z까지의 영문자, 즉 대문자를 의미한다.
>>> pattern = "[a-z]"
>>> regex_pattern = re.compile(pattern, re.IGNORECASE)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
  • re.compile() 안에 re.IGNORECASE(re.I)를 넣으면 대소문자 구분 없이 사용할 수 있다.
>>> pattern = "[a-zA-Z0-9]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['H', 'o', 'r', 'm', 'e', 'l', 'F', 'o', 'o', 'd', 's', '1', '0']
  • "[a-z]", "[A-Z]", "[0-9]"는 한 문자 클래스 안에서 동시에 사용될 수 있다.
>>> pattern = "[가-힣]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['나', '는', '미', '국', '의', '의', '스', '팸', '통', '조', '림', '개', '를', '가', '지', '고', '있', '다']
  • "[가-힣]"을 쓰면 한글에 대해서도 정규 표현식을 사용할 수 있다.
>>> pattern = "[가\-힣]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다. - 힣힣"
>>> print(regex_pattern.findall(Text_data))
['가', '-', '힣', '힣']
  • "\-"처럼 특수 문자 앞에 역슬래시를 사용하면 본래 문자를 그대로 쓸 수 있다.

 

 

2.3. 문자 클래스 [ ]와 반대 특수 문자 "^"

  • 문자 클래스 [ ] 안에 "^"가 들어가면, 그 내용이 반대가 된다.
>>> pattern = "[^가-힣]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' ', ' ', 'H', 'o', 'r', 'm', 'e', 'l', ' ', 'F', 'o', 'o', 'd', 's', ' ', ' ', ' ', '1', '0', ' ', ' ', '.']
  • 한글을 제외한 모든 문자열이 반환된 것을 볼 수 있다.
  • 공백(" ")도 하나의 문자이며, 이는 문자 클래스 안에 띄어쓰기로 표현할 수 있다.
>>> pattern = "[^가-힣 ]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['H', 'o', 'r', 'm', 'e', 'l', 'F', 'o', 'o', 'd', 's', '1', '0', '.']
>>> pattern = "[\^a-z]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['o', 'r', 'm', 'e', 'l', 'o', 'o', 'd', 's']
  • "-"와 마찬가지로 앞에 역슬래시를 붙여 "\^"로 만들면 문자열 자체로 사용할 수 있다.

 

 

2.4. 별도 표기법을 가진 문자 클래스

  • 앞서 학습한 문자 클래스 중 "[a-z]", "[A-Z]", "[0-9]", "[ ]"는 꽤나 빈번하게 사용되는 문자 클래스기 때문에 이를 별도의 표기법으로도 제공한다.
  • 대표적인 문자 클래스는 6가지로 다음과 같다.
문자 클래스 기능 유사한 기능을 하는 문자 클래스
\d 숫자 [0-9]
\D 숫자가 아닌 것 [^0-9]
\s 공백 문자(공백은 단순 띄어쓰기부터 내려쓰기, 탭 등을 포함) [ \t\n\r\f\v]
\S 공백 문자가 아닌 것 [^ \t\n\r\f\v]
\w 문자 + 숫자 + 언더바("_") [a-zA-Z0-9_]
\W 문자 + 숫자 + 언더바("_")가 아닌 것 [^a-zA-Z0-9_]
>>> pattern = "\W"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '.']
  • 위에서 소개한 별도 표기법을 가진 문자 클래스는 [ ] 안에 담을 수도, 따로 사용할 수도 있다.
>>> pattern = "\d"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['1', '0']


>>> pattern = "[\d]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['1', '0']
  • 한 문자 클래스에 대해선 별 의미가 없으나, 두 문자 클래스를 동시에 사용할 땐 크게 다르다.
>>> pattern = "\s\d"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' 1']
  • "\s\d"는 공백 뒤에 숫자가 오는 패턴이므로 '통조림 10개를'에서 ' 1'에 매치된다.
>>> pattern = "[\s\d]"
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' ', ' ', ' ', ' ', ' ', ' ', '1', '0', ' ', ' ']
  • "[\s\d]"는 공백과 숫자를 함께 넣은 문자 클래스 "[ 0-9]"와 같으므로, 다른 결과를 반환한다.

 

 

2.5. 모든 문자를 의미하는 Dot "."

  • "."은 모든 문자열 한자리를 의미한다.
  • 단 "." 은 줄 바꿈 문자 "\n"는 제외한다.
>>> pattern = "\d."
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['10']
  • 숫자 뒤에 문자가 하나 오는 경우인 10을 가지고 왔다.
>>> pattern = ".\d."
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
[' 10']
  • 공백도 Dot에 해당하므로 ' 10'을 가지고 왔다.
>>> pattern = "\d..."
>>> regex_pattern = re.compile(pattern)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['10개를']
  • 추천하는 방법은 아니지만 Dot를 여러 개 붙여서 문자열의 길이도 지정할 수 있다.
  • Dot(".")은 기본적으로 줄 바꿈 문자인 "\n"을 제외하지만, re.DOTALL(re.S) 파라미터를 추가함으로써 줄 바꿈 문자도 포함할 수 있다.
>>> pattern = "\d..."
>>> regex_pattern = re.compile(pattern, re.DOTALL)

>>> Text_data = "나는 미국의 Hormel Foods의 스팸 통조림 10개\n 20개를 가지고 있다."
>>> print(regex_pattern.findall(Text_data))
['10개\n', '20개를']

 

 

 

 

3. 패턴 문자열의 반복 지정

  • 앞서 패턴 문자열을 만드는 법을 배워봤다. 문자 클래스(Character class)는 기본적으로 한 자리이므로, 사용자가 원하는 패턴을 찾아내려면 패턴 문자열의 반복 수를 지정해주면 된다.
  • 반복 수를 지정하는 방법은 다음과 같다.
메타 문자 의미
+ 앞의 패턴 문자열이 1번 이상 반복되어야 한다.
* 앞의 패턴 문자열이 0번 이상 반복되어야 한다.
? 앞의 패턴 문자열은 존재할 수도 있고, 없을 수도 있다(0 이상 1 이하).
{n, m} 앞의 패턴 문자열은 n번 이상 m번 이하 반복되어야 한다.
{n, } 앞의 패턴 문자열은 n번 이상 반복되어야 한다.
{n} 앞의 패턴 문자열을 n번 반복되어야 한다.
  • 앞서 학습한 패턴 문자열 작성 방식과 위의 반복 수를 이용해서 문장 내 원하는 패턴을 찾아보도록 사자.
  • 다음과 같은 문장이 있다고 가정해보자.
>>> Sentence = "마동석씨의 핸드폰 번호는 010-1234-5678, 이메일 주소는 Email_ID.1234@naver.com이다."
  • 여기서 핸드폰 번호를 추출해보자.
  • 핸드폰 번호는 01[016789]-[0-9]{4}-[0-9]{4}의 패턴을 갖는다.
>>> pattern = "01[016789]-[0-9]{4}-[0-9]{4}"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(Sentence))
['010-1234-5678']
  • 아주 쉽게 원하는 패턴을 찾아내었다.
  • 이번에는 이메일 주소를 추출해보자.
  • 이메일 주소의 패턴은 [a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]?[a-z]? 이다.
>>> pattern = "[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]?[a-z]?"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(Sentence))
['Email_ID.1234@naver.com']
  • 이메일 주소의 패턴에는 "?"가 들어갔는데, 이메일 주소의 도메인은 아래와 같이 존재하는 경우도 있기 때문이다.
>>> Sentence = "나문희씨의 이메일 주소는 Email_ID@company.co.kr이다."

>>> pattern = "[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]?[a-z]?"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(Sentence))
['Email_ID@company.co.k']
  • 정규 표현식의 첫 포스트에서 제시한 아래 예제에서 휴대전화 번호, 집 전화번호, 이메일 주소 들을 따로 추출해보자.
data = [
    "010.1234.5678", "010-1234-5678", "02-1234-5678", "031-123-4567", "042.987.6543",
    "emailID01@naver.com", "emailID02@gmail.com", "Email.03@daum.net", "EM_example.01@gmail.com",
    "email_1234@company.co.kr"
]
>>> pattern = "[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]*[a-z]*"
>>> regex_pattern = re.compile(pattern)

>>> print(regex_pattern.findall(data))

  • re 모듈은 기본적으로 하나의 텍스트 데이터에 대해 동작하므로, for문을 이용해서 하나하나씩 비교하고, 해당하는 list에 담아보도록 하자.
# Data가 담길 기본 list를 생성한다.
cellphone_list = []
homephone_list = []
email_list = []


# 각 카테고리별 패턴
cellPhone_pattern = re.compile("01[016789][\-.][0-9]{4}[\-.][0-9]{4}")
homePhone_pattern = re.compile("0[2-6][1-5]?[\-.][0-9]{3,4}[\-.][0-9]{4}")
email_pattern = re.compile("[a-zA-Z]+[a-zA-Z0-9_.]+[@][a-z]+[.][a-z]+[.]*[a-z]*")


for target in data:
    
    cellPhone_Object = cellPhone_pattern.match(target)
    homePhone_Object = homePhone_pattern.match(target)
    email_Object = email_pattern.match(target)
    
    if cellPhone_Object != None:
        
        cellphone_list.append(cellPhone_Object.group())
        
    if homePhone_Object != None:
        
        homephone_list.append(homePhone_Object.group())
        
    if email_Object != None:
        
        email_list.append(email_Object.group())
>>> print(f"휴대전화 번호: {cellphone_list}")
>>> print(f"휴대전화 번호: {homephone_list}")
>>> print(f"휴대전화 번호: {email_list}")
휴대전화 번호: ['010.1234.5678', '010-1234-5678']
휴대전화 번호: ['02-1234-5678', '031-123-4567', '042.987.6543']
휴대전화 번호: ['emailID01@naver.com', 'emailID02@gmail.com', 'Email.03@daum.net', 'EM_example.01@gmail.com', 'email_1234@company.co.kr']
  • 앞서 학습한 패턴 문자열 생성과 패턴 문자열의 반복 횟수만 지정해주니 아주 간단하게 원하는 패턴만 추출하는 데 성공하였다.

 

 

 

 

4. 패턴 문자열의 위치 지정

  • 패턴 문자열이 맨 뒤에 있는지, 맨 앞에 있는지만을 지정할 수 있다.
  • '^': 패턴 문자열 앞에 위치하며, 텍스트의 맨 왼쪽이 해당 패턴 문자열로 시작해야 한다.
  • '$': 패턴 문자열 뒤에 위치하며, 텍스트의 맨 오른쪽이 해당 패턴 문자열로 끝나야 한다.
  • 아래와 같은 이름 데이터가 있다고 가정해보자.
>>> name = "김영수, 김나연, 최영호, 김영식, 박순자, 최정훈, 박성훈, 김상훈, 최현정, 박성민"

>>> name_pattern = re.compile("^김..")
>>> print(name_pattern.findall(name))
['김영수']


>>> name_pattern = re.compile("^최..")
>>> name_pattern.findall(name)
[]


>>> name_pattern = re.compile("박..$")
>>> name_pattern.findall(name)
['박성민']


>>> name_pattern = re.compile("김..$")
>>> name_pattern.findall(name)
[]
  • '^', '$' 패턴은 문자열의 맨 처음과 맨 끝만 중요하게 판단한다.
  • 성이 김으로 시작하는 사람을 찾고 싶다면, 물론 간단한 패턴 문자열 생성으로 해결할 수야 있다.
>>> name_pattern = re.compile("[ ]?김..")
>>> name_pattern.findall(name)
['김영수', ' 김나연', ' 김영식', ' 김상훈']
  • 그러나 간단한 몇 개의 조작만 한다면, '^', '$' 메타 문자만을 이용해서도 찾을 수 있다.
  • 먼저 각 이름의 구분자인 ', '를 줄 바꿈 문자인 '\n'으로 바꿔주자.
>>> name_LF = re.sub(", ", "\n", name)
>>> name_LF
'김영수\n김나연\n최영호\n김영식\n박순자\n최정훈\n박성훈\n김상훈\n최현정\n박성민'
  • re.sub(pattern, repl, string): string에서 pattern에 해당하는 문자열을 repl 문자열로 치환한다.
  • re.compile에 추가 파라미터 re.MULTILINE(re.M)를 하나 넣어줘 보자.
>>> name_pattern = re.compile("^김..", re.MULTILINE)
>>> name_pattern.findall(name_LF)
['김영수', '김나연', '김영식', '김상훈']
  • re.MULTILINE은 문자열 전체의 처음이나 끝이 아닌 각 라인의 처음이나 끝으로 설정해주는 파라미터이다.
  • 즉, '^'와 '$'를 각 줄에 적용할 수 있다는 것이다.
  • 이름이 "훈"으로 끝나는 사람도 쉽게 찾을 수 있다.
>>> name_pattern = re.compile("..훈$", re.MULTILINE)
>>> name_pattern.findall(name_LF)
['최정훈', '박성훈', '김상훈']
  • 이 외에도 "\A", "\Z"가 존재하는데, 각각 "^", "$"와 동일한 기능을 갖는다. 유일한 차이라면, re.MULTILINE 파라미터가 있어도 전체에 대해서 작동한다는 것이다.

 

 

 

 

5. 기타 메타 문자

  • 앞서 소개한 메타 문자 중 "|"와 "()" 이 두 가지를 아직 다루지 않았는데, "|"만 다루고, "()"는 다음 포스팅에서 다루도록 하겠다.
  • "()"는 Grouping이라는 것으로, 꽤나 복잡한 녀석이라 다음 포스트에서 자세히 설명해보도록 하겠다. 이번 포스트에선 "|"까지만 다루겠다.

 

5.1. "|": 또는(Or)

  • "A|B" 패턴 문자열이 있다면, A에 해당하는 패턴 문자열이나 B에 해당하는 패턴 문자열에 대해 가지고 온다.
>>> Text_data = "Banana, Apple, WaterMelon, Melon, StrawBerry"

>>> pattern = "Apple|Banana"
>>> regex_pattern = re.compile(pattern)
>>> print(regex_pattern.findall(Text_data))
['Banana', 'Apple']

 

 

 

지금까지 기본적인 정규 표현식 문법 작성 방법에 대해 알아보았다. 지금까지 다룬 내용만으로도 정규 표현식을 사용하는 것은 크게 어렵지 않으나, 혹시나 정규 표현식에 대해 보다 자세히 학습해보고 싶은 사람이 있을 수도 있으므로, 다음 포스트에서는 정규 표현식에 대한 보다 어려운 부분도 다뤄보도록 하겠다.

728x90
반응형
728x90
반응형

이전 포스트에서 정규 표현식이 무엇인지, 메타 문자가 무엇인지에 대해 가볍게 알아보았다. 앞서 학습한 메타 문자를 예제를 기반으로 제대로 다뤄보려면, 자신이 만든 정규 표현식 패턴이 제대로 만들어졌는지를 확인할 필요가 있다.

이번 포스트에서는 Python에서 정규 표현식을 어떻게 지원하는지 re 모듈을 통해 알아보도록 하겠다. 이번 포스트에서 예제로 정규 표현식으로 만든 패턴을 일부 사용하긴 할 것이나, 이는 다음 포스트에서 자세히 다룰 것이므로 이번에는 참고만 하도록 하자.

 

 

 

 

1. Python과 re 모듈

정규 표현식으로 만든 패턴 문자열이 제대로 작동하는지 알기 위해선 Python에서 기본적으로 제공하는 re 모듈에 대해 알 필요가 있다. 

  • re 모듈은 Python에서 정규 표현식을 지원하기 위해 제공하는 기본 라이브러리이다.
  • re 모듈은 컴파일러 객체를 선언하고, 다음과 같은 메서드를 통해 작동한다.
# re 모듈을 가지고 온다.
import re
pattern = "cat"

regex_pattern = re.compile(pattern)
  • re 모듈의 컴파일 객체는 다음과 같은 메서드를 통해 작동한다.
  • 컴파일 객체는 regex_pattern이 아닌 다른 변수명에 담아도 상관없다.
Method/Attribute 기능
match() 문자열의 왼쪽(시작)부터 컴파일된 정규 표현식 패턴 객체가 일치하는지 판단한다.
search() 문자열 전체에서 정규 표현식 패턴 객체와 일치하는 첫 문자의 위치를 찾는다.
findall() 정규 표현식 패턴 객체와 일치하는 모든 문자열을 찾아 리스트로 반환한다.
finditer() 정규 표현식 패턴 객체와 일치하는 모든 문자열을 찾아 이터레이터(Iterator)로 반환한다.

 

 

 

 

1.1. match()

  • match 함수는 왼쪽(시작)부터 정규 표현식 패턴이 일치하는 곳까지를 반환해준다.
  • 왼쪽부터라는 것이 무슨 뜻인지 아리송한 감이 있는데, 이게 정확히 어떻게 돌아가는지 알아보도록 하자.
  • 다음과 같은 정규 표현식 패턴과 Text_data가 있다고 가정해보자.
>>> pattern = "cats"

>>> regex_pattern = re.compile(pattern)

>>> Text_data = "I love cats and I wish I had cats."
  • Text_data에는 pattern인 cats가 들어 있으므로, 문제없이 찾을 수 있을 것 같다.
  • 이를 match 메서드를 이용해서 탐색해보자.
>>> m = regex_pattern.match(Text_data)
>>> print(m)
None
  • 분명히 "cats"이라는 단어가 Text_data 안에 있음에도 탐색되지 않았다.
  • 이는 match() 메서드가 왼쪽에서부터 탐색하기 때문으로, 주어진 "cats"라는 pattern은 Text_data의 문장 중간에 존재하지, 앞에서부터는 존재하지 않기 때문이다.
  • "cats"가 나오도록 패턴을 조금 수정해보자.
    • 새로운 패턴은 다음 포스트에서 자세히 다룰 것이므로, 일단 따라가 보자!
>>> pattern = ".*cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.match(Text_data)
>>> print(m)
<_sre.SRE_Match object; span=(0, 33), match='I love cats and I wish I had cats'>
  • pattern으로 '.*cats'를 사용하였는데, '.*'는 cats 앞에 문자가 0개 이상 존재한다는 의미다.
  • 즉, 0개 이상 문자가 앞에 존재하는 cats와 일치하는 문장을 탐색한 것이다.
  • match 메서드는 _sre.SRE_Match 객체를 리턴한다.
  • _sre.SRE_Match 객체는 Match 된 문자열의 위치, Match 된 문자열을 반환한다.
  • 이를 통해, Match 메서드 시작부터 패턴이 일치하는지 판단한다는 것을 알 수 있다.

 

 

  • 이번에는 pattern을 아주 조금만 수정해서 match() 메서드의 성질을 보다 자세히 보자.
>>> pattern = ".*cats "
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.match(Text_data)
>>> print(m)
<_sre.SRE_Match object; span=(0, 12), match='I love cats '>
  • 앞서 사용한 pattern 뒤에 스페이스(space)를 추가하자, "I love cats "까지만 출력되었다.
  • 즉, match 메서드는 전체 문장이 얼마나 길든 간에 처음부터 pattern과 일치되는 곳까지만 반환하는 것을 알 수 있다.

 

 

1.1.1. match 객체가 제공하는 메서드

  • 앞서 match는 _sre.SRE_Match 객체를 반환한다고 하였는데, 이는 다음과 같은 메서드를 이용해 내부 정보에 접근할 수 있다.
Method / Attribute 기능
group() 매치된 문자열 출력
start() 매치된 문자열의 시작 지점 출력
end() 매치된 문자열의 끝 지점 출력
span() 매치된 문자열의 시작 지점과 끝 지점을 튜플로 출력
  • 예제를 통해 보도록 하자.
>>> pattern = ".*cats "
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.match(Text_data)

>>> print(m.group())
>>> print(m.start())
>>> print(m.end())
>>> print(m.span())
I love cats 
0
12
(0, 12)

 

 

 

 

1.2. search()

  • search 메서드는 문자열 전체에서 정규 표현식 패턴 객체와 일치하는 첫 문자의 위치를 반환한다.
  • 앞서 만든 예제에 그대로 적용해보도록 하자.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.search(Text_data)
>>> print(m)
<_sre.SRE_Match object; span=(7, 11), match='cats'>
  • search 메서드도 match 메서드와 동일하게 _sre.SRE_Match 객체를 반환한다.
  • search 메서드는 match 메서드와 달리 pattern이 처음부터 일치할 필요는 없으며, Text_data 내 가장 먼저 일치하는 문자열의 위치와 문자열을 반환한다.
  • 만약 존재하지 않는 문자열을 검색하면, match와 마찬가지로 None을 반환한다.
>>> pattern = "dogs"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.search(Text_data)
>>> print(m)
None
  • search() 메서드는 match() 메서드와 동일한 _sre.SRE_Match 객체를 반환하므로, match와 동일한 방법을 통해, 원하는 값을 출력할 수 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.search(Text_data)
>>> print(m.group())
>>> print(m.start())
>>> print(m.end())
>>> print(m.span())
cats
7
11
(7, 11)

 

 

 

 

1.3. findall()

  • findall() 메서드는 가장 설명이 깔끔하면서, 유용한 기능을 제공하는 메서드다.
  • 말 그대로, 문장 안에 원하는 텍스트가 존재하면 그 텍스트를 리스트(List)로 모두 가지고 오는 기능으로, 이를 이용해서 해당 문장 안에 패턴에 일치하는 문자열이 몇 개 존재하는지도 쉽게 확인할 수 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.findall(Text_data)
>>> print(m)
['cats', 'cats']
  • match, search와 달리 리스트를 바로 반환하므로, group, start, end, span과 같은 함수를 사용하지 않는다.

 

 

 

 

1.4. finditer()

  • finditer()는 findall()의 진화 버전이라고 할 수 있는 메서드로, Python 초보자라면 그 기능을 알기 쉽지 않다.
  • finditer()와 findall()의 차이는 결과를 이터레이터(Iterator)로 출력할 것인지 리스트(List)로 출력할 것인지로, List로 출력하면, 사용하기는 단순하나 단지 패턴에 일치하는 단어들을 List에 담아 출력하므로, 그 기능이 한정적이다.
  • 그러나 finditer()는 이터레이터를 통해 값을 반환하므로, 더 많은 정보를 담을 수 있고, 엄청나게 큰 Text_data가 대상일 때, 메모리 측면에서도 훨씬 유리하다.
  • 이터레이터에 대해서는 나중에 자세히 다룰 것이므로, 간단하게 내가 원할 때, 순서대로 값을 반환할 수 있는 녀석이라고 생각하면 된다.
  • 이터레이터는 모든 데이터를 메모리에 올려놓지 않기 때문에 findall()보다 용량을 덜 차지한다는 장점이 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.finditer(Text_data)
>>> print(m)
<callable_iterator object at 0x000001EB3D932358>
  • 출력된 결과가 앞서 봤던 match(), search(), findall()과 다르게 전혀 알아볼 수 없는 녀석이 나왔다.
  • Python의 이터레이터는 next() 또는 for와 같은 함수를 이용해야 값을 출력할 수 있다.
# 첫 번째 next
>>> next(m)
<_sre.SRE_Match object; span=(7, 11), match='cats'>

# 두 번째 next
>>> next(m)
<_sre.SRE_Match object; span=(29, 33), match='cats'>

# 세 번째 next
>>> next(m)

  • next()로 이터레이터 안에 있는 값을 꺼내자 _sre.SRE_Match 객체가 반환되는 것을 볼 수 있다.
  • 이터레이터 안에는 2개의 객체만 존재하므로, 마지막 객체 반환 시, StopIteration을 출력하는 것을 볼 수 있다.
  • 이번에는 for문으로 이터레이터 안의 객체를 꺼내보자.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.finditer(Text_data)

>>> for i in m: 
>>>    print(i)
    
<_sre.SRE_Match object; span=(7, 11), match='cats'>
<_sre.SRE_Match object; span=(29, 33), match='cats'>
  • finditer() 메서드는 findall() 메서드와 달리 _sre.SRE_Match 객체를 반환하므로, 문장 내 패턴에 일치하는 대상들의 위치 정보도 쉽게 반환할 수 있다.
>>> pattern = "cats"
>>> regex_pattern = re.compile(pattern)
>>> Text_data = "I love cats and I wish I had cats."

>>> m = regex_pattern.finditer(Text_data)

>>> for i in m:
>>>    print(i.span())

(7, 11)
(29, 33)

 

 

 

 

이번 포스트에서는 Python에서 기본적으로 제공하는 re 모듈에 대해 한 번 다뤄봤다. 텍스트 데이터를 다룰 때, re 모듈은 매우 유용하게 사용되므로 꼭 숙지하도록 하자.

다음 포스트에서는 메타 문자를 이용하여, 예제 데이터에 대한 정규 표현식 패턴을 만들어보고, re 모듈로 이를 검증해보도록 하자.

728x90
반응형
728x90
반응형

이전 포스트까지 문자열(String)을 다루는 기본적인 방법에 대해 알아보았다. 텍스트 데이터의 양이 적고, 패턴이 매우 단순하다면 앞서 다룬 방법만으로도 충분하겠지만, 이를 넘어서는 텍스트 데이터를 다룰 땐 앞서 다룬 방법만으로는 데이터를 다루기가 보다 곤란하다. 이때 등장하는 것이 문자열이 갖는 특정 규칙을 이용해 패턴을 표현하는 정규 표현식이다.

 

 

 

 

1. 정규 표현식(Regular Expression)이란?

위키피디아에선 정규 표현식을 다음과 같이 설명한다 <출처: 위키백과, 정규 표현식>.


정규 표현식은 특정 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어로, 정규 표현식은 많은 텍스트 편집기와 프로그래밍 언어에서 문자열의 검색과 치환을 위해 지원하고 있으며, 특히 펄과 Tcl은 언어 자체에 강력한 정규 표현식을 구현하고 있다.


이를 이해하기 쉽게 아~~~주 간단하게 표현하면 다음과 같다.

"대상 문자열이 가지고 있는 특정 패턴을 정규 표현식이라는 이름의 패턴 문자열을 이용해서 찾아내는 방법"

당신이 "연락처"라는 데이터를 수집했다고 가정해보자, 응답자들은 단순히 연락처를 적으라고 하였으므로, 휴대전화 번호, 집전화번호, 이메일 주소와 같은 다양한 종류의 연락처를 입력하였고, 당신의 보스는 당신에게 휴대전화 번호와 집전화번호, 이메일 주소를 각각 분리해서 가지고 오라고 지시를 하였다. 애초에 이를 따로 수집했으면 이런 귀찮은 일도 안 생기겠지만, 이미 저질러진 일을 어쩌겠는가!라는 상황에서 유용하게 사용할 수 있는 것이 정규 표현식이다.

정규표현식에 대한 이해를 돕기 위해 수집한 데이터의 예시를 눈으로 보도록 하자.

data = [
    "010.1234.5678", "010-1234-5678", "02-1234-5678", "031-123-4567", "042.987.6543",
    "emailID01@naver.com", "emailID02@gmail.com", "Email.03@daum.net", "EM_example.01@gmail.com",
    "email_1234@company.co.kr"
]

위 예제 데이터를 보면, 휴대전화 번호와 집전화번호, 이메일 주소마다 고유한 패턴을 가지고 있는 것을 볼 수 있다.

  • 다음 패턴은 위 데이터만 대상으로 놓고 분류한 것이다.
  • 휴대전화 번호는 010 특수 기호 '.', '-' 숫자 4자리 '.', '-' 숫자 4자리로 구성되어 있다.
  • 집전화번호는 0 뒤에 1~9의 숫자가 1개에서 2개가 오고 그 뒤에 특수 기호 '.', '-' 숫자 3 ~ 4 자리, '.', '-' 숫자 4자리로 구성되어 있다.
  • 이메일 주소는 ID와 Domain으로 나눠지며, 그 사이에는 '@'가 존재한다.
  • ID는 영문 대, 소문자와 숫자, 특수문자 '.', '_"로 이루어져 있으며, 맨 앞에는 특수문자가 들어갈 수 없다.
  • Domain은 소문자로만 이루어져 있으며, 특수문자 '.'를 기준으로 소문자가 1개에서 2개의 그룹으로 연결된다.

정규 표현식은 위 패턴을 하나의 문법으로 만들어 그 문법에 해당하는 대상을 찾거나 바꿀 수 있는 도구다. 즉, 휴대전화 번호만 찾고 싶다면 휴대전화 번호의 기본적인 패턴을 문법으로 만들고 그 문법에 해당하는 텍스트 데이터를 조회하여, 휴대전화 번호만 가지고 오게 할 수 있다.

 

※ 팁

한 가지 팁이라면, 데이터만 보고 정규 표현식을 사용하는 것보다. 해당 데이터의 생성 방식을 먼저 찾아보고 정규 표현식을 사용하는 것이 일반화 측면에서 더 좋다. 다음과 같은 정보는 잠깐만 인터넷 검색을 해보면 알 수 있는 정보이다.

  • 휴대전화 번호의 통신망 식별번호
    • 010, 011, 016, 017, 018, 019
  • 지역번호
    • 02, 031, 032, 033, 041, 042, 043, 044, 051, 052, 053, 054, 055, 061, 062, 063, 064

 

정규 표현식 문법은 각종 특수문자가 섞여 있고, 문법 작성 방식이 가독성과 거리가 꽤 멀기 때문에 지저분해 보인다는 단점이 있어, 많은 데이터 분석 입문자들이 기피하는 경향이 있다. 그러나, 텍스트 데이터를 다룰 때, 정규 표현식을 다룰 수 있고 없고의 차이는 매우 크므로, 조금 고통스럽더라도 함께 공부해보도록 하자(막상 공부해보면 보기보다 할만하다!).

 

 

 

 

2. 정규 표현식 문법과 메타 문자

  • Python으로 정규 표현식을 다루는 법을 알기 전에 먼저 정규 표현식 문법부터 알아보도록 하자.
  • 정규 표현식은 언어마다 문법이 조금씩 다르므로, 사용하려는 프로그램 언어에 맞는 정규 표현식 문법을 공부하는 것이 좋다.
  • 정규 표현식 사용 방법은 일반적으로 다음과 같은 순으로 진행된다.
  1. 정규 표현식 문법 작성
  2. 작성된 문법을 이용한 조작(탐색, 대체 등)

 

 

2.1. 메타 문자(Meta characters)

  • 평상시, 큰 의미 없이 사용했던 특수 문자들이 정규 표현식에서는 특정한 기능을 갖는데, 이를 메타 문자라고 한다.
  • 정규 표현식 문법은 이 메타 문자와 찾고자 하는 Text 데이터로 구성된다.
  • 이 특수 문자를 문자 그대로 사용하려면 앞에 '\'(역 슬래시)를 넣어줘야 한다.
  • 역 슬래시는 한국인이 사용하는 일반적인 키보드에서 ''로 표기되며, ''는 보이기에만 이렇지 실제로는 역 슬래시로 작용하므로, 이를 그냥 사용해도 문제없다.
  • 메타 문자는 크게 '문자의 패턴', '문자의 반복', '문자열의 위치', '기타 기능'으로 구성된다.
  • 대표적인 메타 문자는 다음과 같다.
문자의 패턴 문자의 반복
[] 문자 클래스 + 앞 문자 패턴이 1개 이상
. 임의의 한 문자 * 앞 문자 패턴이 0개 이상
\d 숫자 ? 앞 문자가 없거나 하나 있음
\D 숫자가 아닌 것 {n, m} 앞 문자 패턴이 n개 이상 m개 이하
\w 문자, 숫자, _ {n, } 앞 문자 패턴이 n개 이상
\W 문자, 숫자, _가 아닌 것 {n} 앞 문자 패턴이 n개
\s 공백 문자    
\S 공백 문자가 아닌 것    

 

 

문자열의 위치 기타 기능
^ 문자열의 시작 | 또는(or)
$ 문자열의 끝 () 문자열 패턴의 그룹화

 

 

이번 포스트에서는 정규 표현식이 무엇인지 알아보고, 정규 표현식에 사용되는 메타 문자가 무엇인지 알아보았다. 다음 포스트에서는 Python에서 정규 표현식의 기능을 어떻게 제공하는지 파악해보도록 하자.

728x90
반응형
728x90
반응형

지금까지 Index와 위치를 이용해서 데이터를 조회하는 방법에 대해 알아보았다. Index, 위치를 이용하여 행을 조회하는 것이 가장 빠르지만, 현실적으로 내가 원하는 데이터의 Index와 행의 위치를 미리 아는 것은 불가능하므로 이번 포스팅에서는 특정 조건으로 내가 원하는 행을 찾아내는 방법에 대해 학습해보도록 하겠다.

 

 

 

Pandas와 Boolean

불리언(Boolean)은 논리 자료형이라고도 하며, 참(True)과 거짓(False)을 나타낼 때 사용된다. Python에서 참(True)은 1, 거짓(False)은 0의 정수 값을 가지며, 이 특성을 잘 활용하면 재밌는 코드를 많이 만들어낼 수 있다.

>>> print(f"True의 Type: {type(True)}")
>>> print(f"False의 Type: {type(False)}")
>>> print("----"*10)
>>> print(f"True를 수치로 표현 시: {int(True)}")
>>> print(f"False를 수치로 표현 시: {int(False)}")
>>> print("----"*10)
>>> print(f"True를 수치로 표현 시: {float(True)}")
>>> print(f"False를 수치로 표현 시: {float(False)}")
True의 Type: <class 'bool'>
False의 Type: <class 'bool'>
----------------------------------------
True를 수치로 표현 시: 1
False를 수치로 표현 시: 0
----------------------------------------
True를 수치로 표현 시: 1.0
False를 수치로 표현 시: 0.0

Pandas는 Boolean Series를 이용하여 True인 행은 출력하고, False인 행은 출력하지 않을 수 있다. 이를 더 쉽게 이야기해보면, "내가 원하는 행만 True로 만들면 그 행만 조회할 수 있다"는 것이다.

 

 

 

 

1. 예제 데이터와 라이브러리 가지고 오기

※ 예제 데이터

이번 포스트에서도 지금까지 사용한 "시험점수.csv" 파일을 예제 데이터로 사용해보도록 하겠다.

사용할 라이브러리는 pandas지만, pandas와 함께 다니는 친구인 numpy도 혹시 쓸 일이 있을지 모르니 가져와주도록 하자.

시험점수.csv
0.00MB

 

>>> import pandas as pd
>>> import numpy as np

>>> FILE_PATH = "시험점수.csv"
>>> 시험점수 = pd.read_csv(FILE_PATH, encoding="euckr")
>>> 시험점수

  • 해당 예제 데이터는 이름, 성별, 학년, 반, 국어, 수학, 영어, 사회, 과학 총 9개의 열과 80개의 행으로 구성되어 있다.
  • 한글이 깨지는 경우, 위 코드처럼 pd.read_csv(File_path, encoding='euckr')로 불러오면 된다.

 

 

 

 

2. Series와 Boolean

  • DataFrame에서 조회할 대상을 가정해보자.

  • 조회할 대상: 3학년이고 2반에 있으며, 국어 점수가 70점 이상인 여학생을 찾는다.

  • 위 대상에 해당하는 속성이 4개나 되므로, 한 번에 찾기는 쉽지 않다.
  • 위 문구를 구성하는 조건을 최소 단위로 쪼개보자.

  • 조건 1: 학년 == 3
  • 조건 2: 반 == 2
  • 조건 3: 국어 >= 70
  • 조건 4: 성별 == "여자"

  • 문구를 최소 단위로 쪼개 보니 꽤 단순해졌다!
  • 방금 정의한 "조회할 대상"은 위 조건 4개에 대해 모두 참(True)인 행이다.
  • 즉, 위 조건 4개에 대해 모두 True인 행을 표현만 할 수 있다면, 위 행에 해당하는 사람의 데이터만 가지고 올 수 있다는 것이다.
  • 지금까지 우리는 Pandas에서 다양한 변수를 담고 있는 데이터(다변량 데이터) 타입인 DataFrame을 다뤄봤는데, 이번엔 단 하나의 변수만 담고 있는 데이터(단변량 데이터) 타입인 시리즈(Series)로 접근을 해보도록 하자.

 

 

 

2.1. 단변량 데이터를 담는 시리즈(Series)

  • 시리즈는 데이터 프레임에서 하나의 컬럼만 뽑으면 쉽게 만들 수 있다.
  • 데이터 프레임 "시험점수"에서 "성별" 시리즈를 뽑아보자.
>>> 시험점수["성별"]
0     남자
1     남자
2     남자
3     남자
4     남자
      ..
75    여자
76    여자
77    여자
78    여자
79    여자
Name: 성별, Length: 80, dtype: object
  • 시리즈는 각 Value에 대한 Index 정보, 시리즈의 이름(Name), 시리즈를 구성하는 원소의 수(Length), 시리즈의 데이터 타입(dtype) 정보를 담고 있다.
  • 이를 하나의 변수만 담고 있는 데이터 프레임이라고 생각해도 큰 문제는 없다.
    • 시리즈는 데이터 프레임에서 사용 가능한 함수의 대부분을 지원하므로, 데이터 프레임을 쓸 줄 안다면 큰 어려움 없이 익숙해질 수 있다.
    • 단, "시리즈 = 하나의 변수만 담긴 데이터 프레임"은 절대 아니다! 주의하도록 하자!
  • 시리즈는 DataFrame을 만들었던 방법처럼 list나 numpy array 등을 이용해 쉽게 만들 수 있다.
>>> Test_list = ["감", "바나나", "포도", "딸기"]
>>> pd.Series(data=Test_list, name="과일")
0      감
1    바나나
2     포도
3     딸기
Name: 과일, dtype: object

 

 

 

 

3. Boolean Series를 이용하여 원하는 행을 조회하기.

3.1. 단변량 데이터인 시리즈에 조건 걸기

  • "시험점수" 데이터 프레임에서 "성별" 시리즈를 뽑고, "여자"인 대상을 찾아보자.
  • Python은 기본적으로 다음과 같은 비교 연산자를 제공한다.
같다 다르다 크다 작다 이상 이하
기호 == != > < >= <=
  • 위 비교 연산자를 이용하여 "성별" == "여자"인 대상자를 찾아보자.
>>> 시험점수["성별"] == "여자"
0     False
1     False
2     False
3     False
4     False
      ...  
75     True
76     True
77     True
78     True
79     True
Name: 성별, Length: 80, dtype: bool
  • 비교 연산자를 사용하자 True와 False로 이루어진 Series가 출력되었다.
  • 이 Series에서 True는 "성별" == "여자"인 행을 뜻하며, False는 "성별" != "여자"인 행을 뜻한다.
  • 위에서 생성한 Boolean 시리즈를 이용해서, 제대로 탐색을 하였는지 확인해보도록 하자.
>>> 시험점수[시험점수["성별"] == "여자"].head(10)

  • 출력되는 결과가 매우 길어 df.head(n) 함수를 이용해 상위 n개의 행만 출력되게 하였다.
  • Pandas의 데이터 프레임은 df[Boolean Series]를 하면, Boolean Series에서 True에 해당하는 행만 출력한다.
  • 앞서, Boolean은 True는 1, False는 0의 값을 갖는다고 하였는데, 이 성질을 이용하면 "성별"=="여자"인 대상의 수를 알 수 있다.
>>> sum(시험점수["성별"] == "여자")
40
  • sum()은 괄호 안의 list, array, series 등의 Data type에 대해 합계를 내주는 함수로, 40이 나왔으므로 1이 40개 있다는 것을 보여준다.
  • 이는 "성별"=="여자"인 행이 40개 있다는 것으로, 여성의 수가 40명이라는 것을 의미한다.
  • 등호가 돌아가는 원리를 알았으니 이번엔 부등호를 조건으로 걸어보자.

 

  • 이번엔 부등호로 조회를 해보자.
  • 이번에는 국어 점수가 70점 이상인 대상을 찾아보자.
>>> 시험점수[시험점수["국어"] >= 70].head(10)

  • 국어 점수가 70점 이상인 상위 10명을 뽑아 보았다.
  • 부등호도 등호와 마찬가지로 쉽게 뽑을 수 있다.

 

 

 

 

3.2. 동시에 2개 이상의 조건 걸기

  • 앞서 시리즈를 이용해 단변량 데이터에 대해 조건을 걸어보았다.
  • 이번엔 2개의 시리즈를 동시에 사용하여 다변량 데이터에 대한 조건을 걸어보도록 하겠다.
  • 이때 사용되는 것이 옛날에 배웠던 집합(Set)이다.
  • Python에서 주로 사용되는 집합은 교집합, 합집합, 차집합이며, 이들은 다음과 같은 기호를 통해 사용할 수 있다.
집합 교집합 합집합 차집합
기호 & | -
  • 2개의 Boolean Series를 가지고 와보자.
>>> 여자_Boolean_Sr = 시험점수["성별"] >= "여자"
>>> 국어_Boolean_Sr = 시험점수["국어"] >= 70

>>> print(여자_Boolean_Sr)
>>> print("----"*10)
>>> print(국어_Boolean_Sr)
0     False
1     False
2     False
3     False
4     False
      ...  
75     True
76     True
77     True
78     True
79     True
Name: 성별, Length: 80, dtype: bool
----------------------------------------
0      True
1     False
2      True
3      True
4      True
      ...  
75    False
76     True
77    False
78    False
79     True
Name: 국어, Length: 80, dtype: bool
  • 두 Boolean 시리즈를 교집합 해보자.
>>> 여자_Boolean_Sr & 국어_Boolean_Sr
0     False
1     False
2     False
3     False
4     False
      ...  
75    False
76     True
77    False
78    False
79     True
Length: 80, dtype: bool
  • 두 시리즈 모두 True인 행만 True로 나오고, 둘 중 하나라도 False인 행은 False가 되었다.
  • 교집합은 두 가지에 모두 해당해야 한다는 의미이므로, 모두 True인 대상을 가지고 온다.
  • 즉, 두 조건에 동시에 해당하는 행만 교집합 시, True로 출력된다.
>>> 시험점수[여자_Boolean_Sr & 국어_Boolean_Sr].head(10)

  • 이를 이용하면, 조건이 아무리 많다고 할지라도 내가 원하는 대상을 구체적으로 찾아낼 수 있다.
  • 남은 조건인 "학년"==3, "반"==1을 추가하여 앞서 선언한 "조회할 대상"을 찾아보자.
>>> 시험점수[
        (시험점수["성별"] == "여자") &
        (시험점수["학년"] == 3) &
        (시험점수["반"] == 1) &
        (시험점수["국어"] >= 70)
        ]

  • 꽤 복잡한 조건이었음에도 Boolean Series를 사용하여 쉽게 찾아내었다.
  • 2개 이상의 Boolean Series를 변수에 담지 않고 바로 조회하려면 꼭 괄호 안에 넣어주기 바란다(괄호 미사용 시 에러 발생).

 

 

 

 

지금까지 조건에 해당하는 행 조회 방법에 대해 알아보았다. 다음 포스트에서는 Pandas의 str을 이용하여, 원하는 문자열에 해당하는 행을 탐색하는 방법에 대해 다뤄보도록 하겠다.

728x90
반응형
728x90
반응형

이전 포스트에서는 DataFrame의 Index를 기반으로 데이터를 조회하는 방법에 대해 알아보았다. 이번 포스트에서는 위치를 기반으로 데이터를 조회하는 슬라이싱(Slicing)과 iloc[]함수에 대해 알아보도록 하겠다.

 

 

 

 

슬라이싱(Slicing)을 이용한 데이터 조회

이전 포스트에서 데이터를 조회하는 방법으로 다음과 같은 방법이 있다고 소개하였다.

  1. 행의 고유한 key인 Index를 이용하여 내가 원하는 데이터 조회
  2. 데이터의 위치를 이용해서 데이터 조회(Slicing)
  3. 조건을 이용한 데이터 조회(Boolean)

이번 포스트에서는 "2. 데이터의 위치를 이용한 데이터 조회(Slicing)"에 대해 다뤄보도록 하겠다.

경우에 따라선 데이터의 위치가 조회에 있어 중요한 key로 작동하기도 한다. 그 예는 다음과 같다.

  • 고등학교 중간고사 전교 30등까지의 데이터를 조회
  • 30명이 있는 반에서 6 ~ 12번째 주번이 누구인지 조회
  • 월 소득의 상위 10% 데이터 조회 또는 하위 10% 조회

위와 같은 위치를 이용한 조회를 하려면 기본적으로 행의 순서가 일정한 규칙에 따라 나열되어 있어야 한다. 데이터가 아무런 규칙 없이 위치해 있다면 사용하기 힘들다. 때문에 규칙대로 데이터를 나열하는 정렬(Sort)과 함께 쓰이는 경우가 잦다.

Pandas의 DataFrame은 기본적으로 0부터 n까지의 index를 순서대로 자동 생성하므로, Index를 기반으로 한 조회와 위치를 이용한 조회의 차이를 알기 어려울 수 있으나 Index와 위치는 다음과 같은 차이를 갖는다.

  1. Index는 단순 행의 위치뿐만 아니라 다양한 정보를 담은 특정한 key로 만들 수 있다.
  2. 위치 기반 조회는 Index로부터 자유롭게 단순 위치로 데이터를 조회할 수 있다.

예제를 통해 위치를 이용한 데이터 조회를 해보도록 하자.

 

 

 

 

1. 예제 데이터 가지고 오기

※ 예제 데이터

이번 포스트에서도 저번 포스트에서 사용한 예제 데이터를 그대로 사용해보도록 하겠다.

시험점수.csv
0.00MB

>>> import pandas as pd
>>> import numpy as np

>>> FILE_PATH = "시험점수.csv"
>>> 시험점수 = pd.read_csv(FILE_PATH, encoding="euckr")
>>> 시험점수

  • 해당 예제 데이터는 이름, 성별, 학년, 반, 국어, 수학, 영어, 사회, 과학 총 9개의 열과 80개의 행으로 구성되어 있다.
  • 한글이 깨지는 경우, 위 코드처럼 pd.read_csv(File_path, encoding='euckr')로 불러오면 된다.

 

 

 

 

2. 순서를 이용해서 데이터를 가지고 오기

  • 순서를 이용해서 데이터를 가지고 오는 방법은 크게 2가지로 다음과 같은 코드를 사용할 수 있다.
  1. df[start:end]
  2. df.iloc[]
  • 두 함수는 기능은 거의 동일하나, 엄밀히 따지면 첫 번째 코드인 df[start:end]를 슬라이싱(Slicing)이라 하며, df.iloc[]는 어려운 말로 명시적 포지션 검색이라 한다.
  • 간단하게 말해서 첫 번째 녀석은 start부터 end까지를 잘라서 보여주는 것이고, 두 번째 녀석은 해당 위치에 있는 것을 가져오는 것으로 이전 포스팅에서 소개한 df.loc[]와 성격이 유사하다.

 

 

2.1. 슬라이싱을 통한 데이터 조회

  • 예제 데이터인 "시험점수"의 10번부터 20번까지 데이터를 조회해보도록 하자.
  • df[start:end]
시험점수[10:20]

  • 아주 간단한 코드를 통해 데이터를 자를 수 있다.
  • 슬라이싱을 통해 가지고 오는 행의 번호는 start 이상, end 미만이다.
  • 슬라이싱은 단순히 처음과 끝만 설정할 수 있는 것이 아니라 간격도 설정해줄 수 있다.
  • DataFrame 시험점수의 10번부터 20번까지를 2를 간격으로 조회해보도록 하자.
  • df[start:end:step]
>>> 시험점수[10:20:2]

  • start나 end 둘 중 하나를 지정해주지 않으면, "start부터 끝까지" 또는 "처음부터 end"까지로 슬라이싱할 수도 있다.
  • 68번째 이후의 데이터를 전부 조회해보고, 처음부터 15번째까지의 데이터를 조회해보도록 하자.
>>> 시험점수[68:]

>>> 시험점수[:15]

  • 여기서도 간격을 설정해줄 수 있다.
>>> 시험점수[:15:2]

 

 

 

 

2.2. 슬라이싱을 이용한 역순 조회

2.2.1. Step을 이용한 역순 조회

  • 슬라이싱의 각 인자들을 음수로 부여하면 역순으로 조회할 수 있다.
  • 전체 데이터를 역순으로 조회하려면 step을 -1로 지정하면 된다.
>>> 시험점수[::-1]

  • step을 -1로 설정하고 start를 지정해주면 start부터 역순 조회도 가능하다.
>>> 시험점수[4::-1]

  • step을 -1로 설정하고 end를 설정하면, 맨 뒤에서부터 시작하여 end까지로 조회된다.
>>> 시험점수[:4:-1]

  • step을 음수로 지정해주면, DataFrame의 순서를 깔끔히 뒤집어 조회하는 것을 볼 수 있다.

 

2.2.2. Step을 지정하지 않은 역순 조회

  • step을 음수로 지정하고 조회하면 데이터가 반대로 뒤집혀서 조회되는 것을 보았다.
  • step을 음수로 지정하지 않고 start나 end만 음수로 지정하면 데이터를 뒤집지 않은 본래의 상태에서 뒤에서부터 조회가 된다.
  • start를 음수 m으로 지정하고 end를 지정하지 않으면 뒤에서부터 m개의 행을 반환한다.
>>> 시험점수[-4:]

  • start를 지정하지 않고 end를 음수 n으로 지정하면 뒤에서부터 n개의 행을 제외하고 반환한다.
>>> 시험점수[:-4]

  • start를 음수 m으로 end를 음수 n으로 입력 시 뒤에서부터 m:n의 행을 가지고 온다.
  • 단, start의 음수 m이 end의 음수 n보다 커야 한다(그렇지 않다면, 행이 하나도 조회되지 않는다).
>>> 시험점수[-7:-4]

>>> 시험점수[-4:-4]

>>> 시험점수[-2:-4]

 

 

 

 

2.3. 슬라이싱과 인덱스

  • 슬라이싱은 기본적으로 행의 순서를 기반으로 행을 조회하지만, 인덱스를 대상으로도 조회가 가능하긴 하다.
  • 시험점수 DataFrame의 "이름"칼럼을 Index로 설정하고, "지원"부터 "민서"까지의 행을 조회해보자.
>>> 시험점수.set_index("이름", inplace=True)
>>> 시험점수["지원":"민서"]

  • 해당 기능은 앞서 다뤘던 Index의 범위를 이용한 조회와 유사하다.
>>> 시험점수.loc["지원":"민서"]

  • 다만, 해당 방법은 중복이 있는 Index에 대해서 Slicing을 할 곳을 특정할 수 없으므로, 동작하지 않는다.
>>> 시험점수.loc["영호":"하윤"]

  • 또, 인덱스를 다른 숫자로 정의한다면, 인덱스를 무시하고 순서로 슬라이싱을 한다.
# 30에서 110까지의 정수를 새로운 인덱스로 정의하였다.
>>> 시험점수.reset_index(drop=False, inplace=True)
>>> 시험점수.index = range(30, 110)
>>> 시험점수

>>> 시험점수[30:40]

  • 30에서 110으로 Index를 바꿨으므로, Index로 슬라이싱 되면 30에서 40까지의 Index로 출력되어야 하나, 30번째 행에서 40번째 행에 위치한 Index인 60~69가 출력되었다.

 

 

 

 

2.4. df.iloc[]를 이용한 조회

  • 앞서 슬라이싱을 이용한 순서 기반 조회를 해보았다.
  • 슬라이싱은 Pandas의 DataFrame, Series뿐만 아니라 list, numpy array 등 Python의 다양한 data type에서 지원하는 기능이지만, Pandas는 여기에 더해서 df.iloc[] 또는 Sr.iloc[] 함수를 통해 위치 기반 조회를 할 수 있다.
>>> 시험점수.reset_index(drop=True, inplace=True)
>>> 시험점수.loc[5]
이름     순자
성별     여자
학년      1
반       1
국어     80
수학    100
영어     75
사회     70
과학     65
Name: 5, dtype: object
>>> 시험점수.loc[5:10]

  • iloc는 기본적으로 슬라이싱과 기능이 매우 유사하므로, 슬라이싱에서 다뤘던 기능들이 대부분 동일하게 실행된다.
  • 단, iloc는 인덱스 기반 조회는 불가능하다.
>>> 시험점수.set_index("이름", inplace = True)
>>> 시험점수.iloc["지원":"민서"]

 

 

 

이번 포스트에서는 위치를 기반으로 필요한 행을 조회하는 슬라이싱과 iloc함수를 알아보았다. 다음 포스트에서는 특정 조건을 기반으로 행을 조회하는 Boolean 기반 행 조회 방법에 대해 알아보도록 하자.

728x90
반응형

+ Recent posts