DataFrame과 같은 테이블을 결합할 때, pd.concat 함수를 사용해서 축 방향으로 결합할 수도 있지만,

이 방법외에도 결합을 위해 자주 사용하는 방법은 pd.merge 함수를 활용하는 것입니다.

간단한 예제를 통해 네 가지의 Join 방법을 살펴봅니다.

  • Inner Join
  • Outer Join
  • Left Join
  • Right Join

먼저 테이블은 다음과 같이 정의합니다.

import pandas as pd

A = pd.DataFrame({'ID' : [1, 2, 3], 
                  '이름' : ['김우주', '오수아', '박새로이']})
B = pd.DataFrame({'ID' : [2, 3, 4], '주소' : ['서울', '장가', '이태원']})

 

Inner Join

Inner Join은 두 테이블의 키가 일치하는 데이터만 추출해서 반환합니다. 우리는 'ID'를 키로 활용할 것입니다.
두 테이블에서 일치하는 아이디는 [2, 3]이기 때문에, 2행을 가지는 테이블을 만들 수 있습니다.

pd.merge(A, B, on = 'ID', how = 'inner')

Outer Join

Outer Join은 키를 기준으로 두 테이블에 존재하는 모든 데이터를 활용해서 결합하는 방법입니다. 따라서 A 테이블에서는 ID=4가 없으므로 NaN 값으로 채워질 것이고, B 테이블에서는 ID=1이 없으므로 NaN 값으로 채워질 것입니다.
쉽게 생각하면 조건은 잘 모르겠고, Null 값으로 채워서라도 다 사용하겠다~ 라는 의미입니다.

pd.merge(A, B, on = 'ID', how = 'outer')

 

Left Join

Left Join은 left에 위치하는 테이블의 키만 사용하여 데이터를 반환합니다. A 테이블이 보유하고 있는 키를 right 테이블은 B가 보유하고 있지 않으면 무시됩니다. 밑의 결과에서 ID=4가 반환되지 않는 것을 볼 수 있습니다.

pd.merge(left = A, right = B, on = 'ID', how = 'left')

 

Right Join

Right JoinLeft Join의 반대입니다. B 테이블의 키만 반환합니다.

pd.merge(left = A, right = B, on = 'ID', how = 'right')

Convolution 구현해보기

먼저, 3x3 필터를 정의합니다. 어디선가 많이 보셨을 법한 필터입니다.(가로선을 검출하는)

filter = [[-1, 0, 1], 
        [-2, 0, 2], 
        [-1, 0, 1]]

 

벡터화(Vectorization)을 수행하지 않는 컨볼루션의 구현은 매우 간단합니다. for-loop 2개로 구현할 수 있습니다.

컨볼루션은 이미지의 영역과 컨볼루션 필터를 element-wise 곱한 뒤, 전부 합칩니다. 다음 코드를 보면 알 수 있습니다.
패딩을 사용하지 않기 때문에 시작 지점은 이미지의 (1, 1) 좌표입니다.

x_shape = img.shape[0]
y_shape = img.shape[1]

for x in range(1,x_shape-1):
  for y in range(1,y_shape-1):
      convolution = 0.0
      convolution = convolution + (img[x - 1, y-1] * filter[0][0])
      convolution = convolution + (img[x, y-1] * filter[0][1])
      convolution = convolution + (img[x + 1, y-1] * filter[0][2])
      convolution = convolution + (img[x-1, y] * filter[1][0])
      convolution = convolution + (img[x, y] * filter[1][1])
      convolution = convolution + (img[x+1, y] * filter[1][2])
      convolution = convolution + (img[x-1, y+1] * filter[2][0])
      convolution = convolution + (img[x, y+1] * filter[2][1])
      convolution = convolution + (img[x+1, y+1] * filter[2][2])

      if(convolution < 0):
        convolution = 0
      elif(convolution > 255):
        convolution = 255
  • x_shape와 y_shape는 이미지의 크기를 나타냅니다.
  • for-loop 끝의 if문은 이미지 값의 범위 0 ~ 255를 위해 사용합니다.
  • 아래 테이블에서 위의 코드를 좀 더 편하게 볼 수 있습니다.
img[x-1, y-1] * filter[0][0] img[x, y-1] * filter[0][1] img[x+1, y-1] * filter[0][2]
img[x-1, y] * filter[1][0] img[x, y] * filter[1][1] img[x+1, y] * filter[1][2]
img[x-1, y+1] * filter[2][0] img[x, y+1] * filter[2][1] img[x+1, y+1] * filter[2][2]

 

MaxPooling 구현해보기

흔히 사용하는 (2,2)의 MaxPooling을 구현해보도록 합니다.

new_x_shape = int(x_shape / 2)
new_y_shape = int(y_shape / 2)

# 풀링이 적용될 이미지를 만듭니다.
pooled_img = np.zeros((new_x_shape, new_y_shape))

# 2는 stride를 나타냅니다.
for x in range(0, x_shape, 2):
	for y in range(0, y_shape, 2):
    	pixels = []
        pixels.append(img[x, y])
        pixels.append(img[x + 1, y])
        pixels.append(img[x, y + 1])
        pixels.append(img[x + 1, y + 1])
        # 최댓값만 저장합니다.
        pooled_img[int(x/2), int(y/2)] = max(pixels)
img[x, y] img[x + 1, y]
img[x, y + 1] img[x + 1, y + 1]

 

 

os.rename()을 사용한다.

import os

os.rename('./test.txt', './rename.txt')

파싱할 xml은 다음과 같습니다.

ml_string = '''<?xml version="1.0"?> 
<data> 
  <tool name="Keras"> 
      <rank>1</rank> 
      <content>good</content>
      <merge name="TensorFlow" year="2020"/>
  </tool> 
  <tool name="TensorFlow"> 
      <rank>1</rank>
      <content>nice</content>
  </tool>
  
  <tool2 name="PyTorch"> 
      <rank>2</rank> 
      <content>research</content>
  </tool2> 
  <tool2 name="MXNet"> 
      <rank>3</rank> 
      <content>well</content>
  </tool2> 
</data> 
'''

ElementTree를 통해 xml 파일을 지정하고 root에 접근하는 방법은 다음과 같습니다.

tree = elemTree.parse('ml.xml')
root = tree.getroot()

* 여기서는 예제를 위해 string을 사용하므로 다음을 사용합니다. 파일을 통한 파싱은 위 코드로 해야합니다.
  아래 코드는 관련 없습니다.

root = ET.fromstring(ml_string)

 

태그와 태그가 가지고 있는 속성은 다음과 같이 확인할 수 있습니다.

print(root.tag, root.attrib)
  • data {}

 

ElementTree에서는 대표적으로 find(), findall(), iter() 함수를 많이 사용합니다.

find()

print(root.find('tool'))
print(root.find('tool').tag, '|', root.find('tool').attrib)

# 하위 태그를 탐색할 수 있습니다.
for child in root.find('tool'):
    print(child.tag, child.attrib)
  • <Element 'tool' at 0x000001E489FB5AE8>
    tool | {'name': 'Keras'}
    rank {}
    content {}
    merge {'name': 'TensorFlow', 'year': '2020'}
  • root.find('tag_name')은 여러 개의 태그 중에서 가장 첫 번째 태그를 가져옵니다.
  • for-loop를 통해 하위 태그를 확인하고 있습니다.

 

findall()

print(root.findall('tool'))

# <tool/> 태그를 전부 탐색할 수 있습니다.
for root_e in root.findall('tool'):
    print(root_e.tag, root_e.attrib)
  • [<Element 'tool' at 0x000001E489FB5AE8>, <Element 'tool' at 0x000001E489FBE228>]
    tool {'name': 'Keras'}
    tool {'name': 'TensorFlow'}
  • 원하는 태그를 전부 찾아서 리스트 형태로 얻을 수 있습니다. 하지만 하위 태그는 찾아주지 않습니다.

iter()

# <tool2/> 태그를 전부 탐색할 수 있습니다.
for root_e in root.iter('tool2'):
    print(root_e.tag, root_e.attrib)
  • tool2 {'name': 'PyTorch'}
    tool2 {'name': 'MXNet'}
  • find, findall과 다르게 상위 또는 하위 태그를 전부 찾아줍니다.

 

다음은 원하는 두 가지 태그를 찾고, 태그의 text를 변경하여 저장하는 것까지의 예제 코드입니다.

def parse_xml(xml_list):
    for xml_name in xml_list:
        xml = './' + xml_name
        attached_name = 'abc'
        
        tree = elemTree.parse(gml)
        root = tree.getroot()
        
        # 두 개 태그의 text를 수정한다고 가정합니다.
        for attr1, attr2 in zip(root.iter('attr_1'), root.iter('attr_2')):
            attr1_newtext = attached_name + attr1.text
            attr2_newtext = attached_name + attr2.text
            
            attr1.text = attr1_newtext
            attr2.text = attr2_newtext
        
        # 수정한 xml 파일을 저장합니다.
        tree.write('./' + xml_name)

 

파싱할 xml에 namespace가 존재하는 경우

간혹 xml에 다음과 같이 namespace가 존재하는 경우가 있다.

다음과 같이 namespace를 등록하지 않으면, ElementTree가 자동으로 ns0, ns1, ...으로 등록하기 때문에 주의해야 한다.

ET.register_namespace("gml", "http://www.opengis.net/gml")
ET.register_namespace("bldg", "http://www.opengis.net/citygml/building/2.0")
ET.register_namespace("app", "http://www.opengis.net/citygml/appearance/2.0")
ET.register_namespace("core", "http://www.opengis.net/citygml/2.0")

다음과 같이 사용한다.

import numpy as np
x = np.array([1, 4, 5, 1000, 100, 3, 10, 2])
print(x)
print('----------------------')
print('partition',np.partition(x, -3))
print('partition',np.partition(x, -1))
print(np.argpartition(x, -3))
print(x[np.argpartition(x, -3)])
print('----------------------')
print('partition', np.partition(x, 3))
print(np.argpartition(x, 3))
print(x[np.argpartition(x, 3)])
print('----------------------')
print(np.partition(x, (1, 2)))
print(np.argpartition(x, (1, 3)))
print(x[np.argpartition(x, (1, 5))])

츌력 결과

np.partition에 대해서 설명하면, np.partition(np.array(), k-th)로 사용하는데

np.partition(~, 2)와 같은 경우는 리스트에서 순서 상관없이 작은 숫자 2개를 뽑아 왼쪽으로 놓겠다는 의미

np.partition(~, -2)와 같은 경우는 리스트에서 순서 상관없이 큰 값 2개를 뽑아 오른쪽으로 놓겠다는 의미이다.

np.argpartition은 partition과 같고, index를 리턴한다.

 

+ 틀리면 댓글 부탁드립니다.