그래프에서 중요한 값들이 특정 범위에 몰려있으면 확대를 해야 합니다.
matplotlib의 object oriented API는
.set_xlim()과.set_ylim()을 제공해 줍니다.jupyter notebook에서
%matplotlib qt로 선언했다면 GUI로 자유롭게 지정할 수도 있습니다.
그러나 잘 보이지도 않는 영역을 찾기 위해 반복적으로 영역을 설정하는 일은 지루합니다.
데이터 숫자라도 많으면 다시 그릴때마다 기다려야 하는 과정이 결코 즐겁지 않습니다.
데이터의 분포를 이용해 관심영역(Region of Interest)를 자동으로 확대하는 함수를 만들겠습니다.
1. sample data
- 예제 데이터는
xs=[x1, x2, x3, x4, x5],ys=[y1, y2, y3, y4, y5]로 미리 정의되었습니다.- 여기에서 다운로드 받으실 수 있습니다.
xs와ys의 원소는 모두 1차원numpy.ndarray()입니다.- 첫 번째와 마지막을 제외하면 y값이 특정 범위에 몰려 있음을 알 수 있습니다.
1 | for i in range(1,6): # 변수 이름을 동적으로 할당 |

- 몇 차례의 iteration을 통해 얻은 최적 범위로 그림을 그리면 이렇게 됩니다.
1 | ax[1].set_ylim(-0.015,0) # iteration 3회 결과 |

- 두 번째 ~ 네 번째 그래프에서 전체 범위에 묻혀있던 관심영역이 잘 드러났습니다.
- 첫 번째와 마지막 그래프는 전체가 관심영역이므로 가려지면 안 됩니다.
- 이런 범위 설정 작업을 반복하지 않도록 자동화하고 싶습니다.
2. data distribution
- 두 번째 ~ 네 번째 그래프에서 관심영역 외 부분이 단조증가 또는 감소하고 있습니다.
- 데이터의 빈도를 이용해 이 부분을 제거하고 관심영역에 집중합시다.
numpy.histogram()으로 y 데이터의 구간별 데이터 수를 확인합니다.
1 | res = 1000 |

- 두 번째 이후의 그래프에서, 진동하는 구간에 많은 데이터가 몰린 것을 확인할 수 있습니다.
- 1000개 구간으로 충분히 잘게 나누었기 때문에 데이터가 없는 구간도 있습니다.
3. setting $$y$$ range by bin filtering
- 구간별 데이터 수가 일정(
yth) 이상인 구간만 추립니다. - 그러나 첫 번째 그래프처럼 데이터 수는 적어도 전체를 다 보여줘야 하는 경우가 있습니다.
- 이럴 때를 대비하여 관심영역이 딱히 없다면 전체 구간을 잡도록 예외처리합니다.
1 | res = 1000 |

- y 범위가 너무 빡빡하게 잡혔네요. 여유를 좀 줍시다.
- y range를 계산해서, 사전에 정의된 여유분(
ymargin) 비율만큼 위아래로 더해줍니다.
1 | res = 1000 |

4. setting $$x$$ range by $$y$$ range
두 번째 ~ 네 번째 그래프 오른쪽이 비어있습니다.
관심영역이 왼쪽으로 치우친 탓입니다. x 범위도 다시 설정해서 그림을 꽉 차게 만들어줍시다.
위에서 만든 코드를 다시 사용하기 좋도록 함수로 정의하겠습니다. 주석도 달고요.
(2020.05.24) 버그를 발견하여 Bresenham’s line algorithm을 추가했습니다.
1 | class bresenham: |
- 이렇게 함수를 만들어두면 본 코드가 매우 짧아집니다.
1 | fig, axes = plt.subplots(ncols=5, figsize=(12,3)) |

모든 그림이 관심영역에 집중되었습니다.
그러나 네번째 그림이 위쪽에 치우친 것이 영 마음에 걸리네요.
데이터 수로 구간을 설정해서 네 번째 데이터 우측의 진동이 잡혀버렸습니다.
네 번째 그래프에만 인자를 다르게 설정해서 가운데로 옮깁시다.
훨씬 엄격한 조건(
yth=3)과 관대한 여백(ymargin=0.2)을 조합합니다.
1 | fig, axes = plt.subplots(ncols=5, figsize=(12,3)) |

- 이제 웬만한 그래프는 자동으로 관심영역을 확대할 수 있습니다.
- 그러나 관심영역 밖에 중요한 메시지가 있을 수 있습니다.
- 반드시 전체를 먼저 확인한 후에 확대하고, 확대 후에도 제대로 됐는지 확인합시다.
5. Bug fix : missing bins (2020.05.24.)
- 간혹 아래와 같이 전혀 의도치 않은 구간으로 확대되는 경우가 있었습니다.
1 | x = np.linspace(-2.25, 2.25, 61) |

- 데이터를 선그래프로 표현하면 점과 점 사이가 이어져 데이터가 밀집한 구간을 히스토그램으로 처리할 수 있을 듯 하지만, 실제로 선에 해당하는 구간은 데이터가 존재하지 않아 빈도 측정시 관심영역으로 감지될 수 없습니다.
- 데이터가 교묘하게(?) 조금씩 위아래로 어긋나있을 때 이런 일들이 발생하는데, 이를 방지하기 위해 선을 전부 데이터로 채워주었습니다.
- 무한한 경우의 수를 모두 채울 수는 없으므로 공간을 적당히 이산화하고, 여기에 Bresenham’s line algorithm을 차용하였습니다.
5.1. Data quantization in different scales
- 데이터를 정수 공간으로 매핑합니다: x, y scale이 달라졌습니다.
1 | xd = np.digitize(x, np.linspace(x.min(), x.max(), len(x))) |

5.2. Application of Bresenham’s line algorithm
- 정수 공간에서 점 사이를 메웁니다.
1 | points = list(zip(xd, yd)) |

5.3. Map to Original scale
- 정수 공간에 메워진 데이터를 다시 원래의 공간으로 가져옵니다.
1 | def scale_inv(xscaled, x_num, x_arr): |

5.4. apply (previous) get_focus()
- 변환된 데이터에 앞의
get_focus()를 그대로 적용합니다. - 기대한 그래프(expected)와 비교했을 때 상당히 합리적인 범위로 조정되었음을 알 수 있습니다.
1 | ax[3].plot(x, y, 'o-', c='g') |

6. summary
- 위 5.1.~5.4.를
get_focus()안에 모두 탑재하였습니다. - 본 기능을 사용하실 때는
class bresenham과 함께get_focus()를 그대로 사용하시면 됩니다.