hossam 0.3.0__tar.gz → 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hossam
3
- Version: 0.3.0
3
+ Version: 0.3.4
4
4
  Summary: Hossam Data Helper
5
5
  Home-page: https://github.com/leekh4232/hossam-data
6
6
  Author: Lee Kwang-Ho
@@ -12,7 +12,7 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Topic :: Software Development :: Libraries
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3 :: Only
15
- Requires-Python: >=3.8
15
+ Requires-Python: >=3.11
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: tqdm
@@ -29,6 +29,7 @@ Requires-Dist: scikit-learn
29
29
  Requires-Dist: pingouin
30
30
  Requires-Dist: statannotations
31
31
  Requires-Dist: joblib
32
+ Requires-Dist: geopandas
32
33
  Dynamic: author
33
34
  Dynamic: author-email
34
35
  Dynamic: classifier
@@ -124,6 +125,33 @@ from hossam import load_data, load_info
124
125
  datasets = load_info()
125
126
  print(datasets)
126
127
 
128
+ ## 🧭 문서(호스팅)
129
+
130
+ - 실시간 문서: https://py.hossam.kr
131
+ - 문서 생성 방식: MkDocs(Material) + mkdocstrings로 코드에서 API 레퍼런스를 자동 생성합니다.
132
+ - 로컬 미리보기:
133
+
134
+ ```bash
135
+ pip install mkdocs mkdocs-material "mkdocstrings[python]" pymdown-extensions mkdocs-autorefs
136
+ mkdocs serve
137
+ ```
138
+
139
+ - 정적 사이트 빌드:
140
+
141
+ ```bash
142
+ mkdocs build
143
+ ```
144
+
145
+ - 배포(깃허브 페이지 + 커스텀 도메인):
146
+
147
+ ```bash
148
+ mkdocs gh-deploy --clean --cname py.hossam.kr
149
+ ```
150
+
151
+ - 구성 파일과 문서 위치:
152
+ - MkDocs 설정: [mkdocs.yml](mkdocs.yml)
153
+ - 문서 소스: [docs/](docs)
154
+
127
155
  # 특정 키워드로 검색
128
156
  ad_datasets = load_info(search="AD")
129
157
 
@@ -80,6 +80,33 @@ from hossam import load_data, load_info
80
80
  datasets = load_info()
81
81
  print(datasets)
82
82
 
83
+ ## 🧭 문서(호스팅)
84
+
85
+ - 실시간 문서: https://py.hossam.kr
86
+ - 문서 생성 방식: MkDocs(Material) + mkdocstrings로 코드에서 API 레퍼런스를 자동 생성합니다.
87
+ - 로컬 미리보기:
88
+
89
+ ```bash
90
+ pip install mkdocs mkdocs-material "mkdocstrings[python]" pymdown-extensions mkdocs-autorefs
91
+ mkdocs serve
92
+ ```
93
+
94
+ - 정적 사이트 빌드:
95
+
96
+ ```bash
97
+ mkdocs build
98
+ ```
99
+
100
+ - 배포(깃허브 페이지 + 커스텀 도메인):
101
+
102
+ ```bash
103
+ mkdocs gh-deploy --clean --cname py.hossam.kr
104
+ ```
105
+
106
+ - 구성 파일과 문서 위치:
107
+ - MkDocs 설정: [mkdocs.yml](mkdocs.yml)
108
+ - 문서 소스: [docs/](docs)
109
+
83
110
  # 특정 키워드로 검색
84
111
  ad_datasets = load_info(search="AD")
85
112
 
@@ -0,0 +1,279 @@
1
+ """GIS 관련 유틸리티 함수 모음.
2
+
3
+ 주소 지오코딩, Shapefile 로드/저장 등의 기능을 제공합니다.
4
+ """
5
+
6
+ import requests
7
+ import concurrent.futures as futures
8
+ from pandas import DataFrame
9
+ import pandas as pd
10
+ from tqdm.auto import tqdm
11
+ import time
12
+ from geopandas import GeoDataFrame, read_file
13
+ import geopandas as gpd
14
+ from pyproj import CRS
15
+ import os
16
+ import warnings
17
+
18
+ from .util import hs_pretty_table
19
+
20
+ def __geocode_item(session, index: int, addr: str, key: str) -> tuple[float, float]:
21
+ """단일 주소를 VWorld API로 지오코딩합니다.
22
+
23
+ Args:
24
+ session: 재사용할 `requests.Session` 인스턴스.
25
+ index: 입력 데이터의 인덱스(로그용).
26
+ addr: 지오코딩할 도로명 주소 문자열.
27
+ key: VWorld API 키.
28
+
29
+ Returns:
30
+ (latitude, longitude) 튜플.
31
+
32
+ Raises:
33
+ ValueError: 주소가 비어있거나 잘못된 경우.
34
+ requests.exceptions.RequestException: 주소를 찾지 못한 경우 등 요청 관련 오류.
35
+ Exception: HTTP 오류 코드나 API 내부 오류 등 기타 예외.
36
+ """
37
+ if not addr or addr == "nan":
38
+ raise ValueError(
39
+ "⚠️[Warning] 주소가 존재하지 않습니다. (%d) -> %s" % (index, addr)
40
+ )
41
+
42
+ url: str = f"https://api.vworld.kr/req/address"
43
+ params = {
44
+ "service": "address",
45
+ "request": "getCoord",
46
+ "key": key,
47
+ "address": addr,
48
+ "type": "ROAD",
49
+ "format": "json",
50
+ }
51
+
52
+ response = None
53
+
54
+ try:
55
+ response = session.get(url, params=params, timeout=(3, 30))
56
+ except Exception as e:
57
+ raise e
58
+
59
+ if response.status_code != 200:
60
+ raise Exception(
61
+ "⚠️[%d-Error] %s - API 요청에 실패했습니다. (%d) -> %s"
62
+ % (response.status_code, response.reason, index, addr)
63
+ )
64
+
65
+ response.encoding = "utf-8"
66
+ result = response.json()
67
+ status = result["response"]["status"]
68
+
69
+ if status == "ERROR":
70
+ error_code = result["response"]["error"]["code"]
71
+ error_text = result["response"]["error"]["text"]
72
+ raise Exception(f"[{error_code}] {error_text} (%d) -> %s" % (index, addr))
73
+ elif status == "NOT_FOUND":
74
+ raise requests.exceptions.RequestException(
75
+ "⚠️[Warning] 주소를 찾을 수 없습니다. (%d) -> %s" % (index, addr)
76
+ )
77
+
78
+ longitude = float(result["response"]["result"]["point"]["x"])
79
+ latitude = float(result["response"]["result"]["point"]["y"])
80
+ result = (latitude, longitude)
81
+ #print("%s --> (%s, %s)" % (addr, latitude, longitude))
82
+ return result
83
+
84
+
85
+ def hs_geocode(df: DataFrame, addr: str, key: str) -> DataFrame:
86
+ """주소 컬럼을 일괄 지오코딩하여 위도/경도 컬럼을 추가합니다.
87
+
88
+ Args:
89
+ df: 입력 `DataFrame`.
90
+ addr: 주소가 들어있는 컬럼명.
91
+ key: VWorld API 키.
92
+
93
+ Returns:
94
+ 위도(`latitude`), 경도(`longitude`) 컬럼이 추가된 `DataFrame`.
95
+
96
+ Raises:
97
+ Exception: 지오코딩 과정에서 발생한 예외를 전파합니다.
98
+ """
99
+ data: DataFrame = df.copy()
100
+ size: int = len(data)
101
+ success = 0
102
+ fail = 0
103
+
104
+ print("ℹ️요청 데이터 개수: %d" % size)
105
+
106
+ with tqdm(total=size, colour="yellow") as pbar:
107
+ with requests.Session() as session:
108
+ with futures.ThreadPoolExecutor(max_workers=30) as executor:
109
+ for i in range(size):
110
+ time.sleep(0.1)
111
+ address: str = str(data.loc[i, addr]).strip()
112
+
113
+ p = executor.submit(
114
+ __geocode_item, session, index=i, addr=address, key=key
115
+ )
116
+
117
+ try:
118
+ result = p.result()
119
+ latitude, longitude = result
120
+ data.loc[i, "latitude"] = latitude
121
+ data.loc[i, "longitude"] = longitude
122
+ success += 1
123
+ except requests.exceptions.RequestException as re:
124
+ print(re)
125
+ data.loc[i, "latitude"] = None
126
+ data.loc[i, "longitude"] = None
127
+ fail += 1
128
+ except ValueError as ve:
129
+ print(ve)
130
+ data.loc[i, "latitude"] = None
131
+ data.loc[i, "longitude"] = None
132
+ fail += 1
133
+ except Exception as e:
134
+ fail += 1
135
+ executor.shutdown(wait=False, cancel_futures=True)
136
+ raise e
137
+ finally:
138
+ pbar.set_postfix({"success": success, "fail": fail})
139
+ pbar.update(1)
140
+
141
+ data["latitude"] = data["latitude"].astype(float)
142
+ data["longitude"] = data["longitude"].astype(float)
143
+
144
+ print(f"✅총 {size}개의 데이터 중 {success}개의 데이터가 처리되었습니다.")
145
+
146
+ return data
147
+
148
+
149
+ def hs_load_shape(path: str, info: bool = True) -> GeoDataFrame:
150
+ """Shapefile을 읽어 `GeoDataFrame`으로 로드합니다.
151
+
152
+ Args:
153
+ path: 읽을 Shapefile(.shp) 경로.
154
+ info: True면 데이터 프리뷰와 통계를 출력.
155
+
156
+ Returns:
157
+ 로드된 `GeoDataFrame`.
158
+
159
+ Raises:
160
+ FileNotFoundError: 파일이 존재하지 않는 경우.
161
+ """
162
+ if not os.path.exists(path):
163
+ raise FileNotFoundError("⚠️[FileNotFoundException] 주어진 파일을 찾을 수 없습니다.\n - %s" % path)
164
+
165
+ data = read_file(path)
166
+
167
+ if info:
168
+ print("\n✅ 테이블 정보")
169
+ hs_pretty_table(data.info(), tablefmt="pretty")
170
+
171
+ print("\n✅ 상위 5개 행")
172
+ hs_pretty_table(data.head(), tablefmt="pretty")
173
+
174
+ print("\n✅ 하위 5개 행")
175
+ hs_pretty_table(data.tail(), tablefmt="pretty")
176
+
177
+ print("\n📊 기술통계")
178
+ desc = data.describe().T
179
+ desc["nan"] = data.isnull().sum()
180
+ hs_pretty_table(desc, tablefmt="pretty")
181
+
182
+ return data
183
+
184
+ def hs_save_shape(
185
+ gdf: GeoDataFrame | DataFrame,
186
+ path: str,
187
+ crs: str | None = None,
188
+ lat_col: str = "latitude",
189
+ lon_col: str = "longitude",
190
+ ) -> None:
191
+ """전처리된 데이터(GeoDataFrame 또는 DataFrame)를 Shapefile 또는 GeoPackage로 저장합니다.
192
+
193
+ - GeoDataFrame 입력:
194
+ - CRS가 있으면 그대로 유지합니다.
195
+ - CRS가 없으면 `crs`(기본 WGS84)를 지정합니다.
196
+ - DataFrame 입력:
197
+ - 오직 이 경우에만 `lat_col`, `lon_col`을 사용해 포인트 지오메트리를 생성합니다.
198
+ - 좌표가 유효하지 않은 행은 제외되며, 유효한 좌표가 하나도 없으면 예외를 발생시킵니다.
199
+
200
+ 파일 형식:
201
+ - .shp: ESRI Shapefile (필드명 10자 제한, ASCII 권장)
202
+ - .gpkg: GeoPackage (필드명 제약 없음, 한글 가능)
203
+ - 확장자 없으면 .shp로 저장
204
+
205
+ Args:
206
+ gdf: 저장할 `GeoDataFrame` 또는 `DataFrame`.
207
+ path: 저장 경로(.shp 또는 .gpkg, 확장자 없으면 .shp 자동 추가).
208
+ crs: 좌표계 문자열(e.g., "EPSG:4326"). 미지정 시 WGS84.
209
+ lat_col: DataFrame 입력 시 위도 컬럼명.
210
+ lon_col: DataFrame 입력 시 경도 컬럼명.
211
+
212
+ Raises:
213
+ TypeError: 입력 타입이 잘못된 경우.
214
+ ValueError: 경로가 잘못되었거나 CRS가 유효하지 않은 경우,
215
+ 또는 DataFrame에서 유효 좌표가 하나도 없는 경우.
216
+ """
217
+ if gdf is None or not isinstance(gdf, (GeoDataFrame, DataFrame)):
218
+ raise TypeError("⚠️[TypeError] GeoDataFrame 또는 DataFrame 타입의 데이터가 필요합니다.")
219
+
220
+ if not path or not isinstance(path, str):
221
+ raise ValueError("⚠️[ValueError] 저장할 경로(path)가 올바르지 않습니다.")
222
+
223
+ # 기본 좌표계를 WGS84로 설정
224
+ crs_input = crs if crs and str(crs).strip() else "EPSG:4326"
225
+
226
+ try:
227
+ target_crs = CRS.from_user_input(crs_input)
228
+ except Exception as e:
229
+ raise ValueError(f"⚠️[ValueError] 유효하지 않은 좌표계 값입니다: {crs_input}") from e
230
+
231
+ # DataFrame인 경우 위경도 컬럼으로 포인트 지오메트리 생성
232
+ if isinstance(gdf, DataFrame) and not isinstance(gdf, GeoDataFrame):
233
+ if lat_col not in gdf.columns or lon_col not in gdf.columns:
234
+ raise ValueError(
235
+ f"⚠️[ValueError] DataFrame에 '{lat_col}', '{lon_col}' 컬럼이 필요합니다."
236
+ )
237
+
238
+ df = gdf.copy()
239
+ # 숫자 변환 및 결측 제거
240
+ df[lat_col] = pd.to_numeric(df[lat_col], errors="coerce")
241
+ df[lon_col] = pd.to_numeric(df[lon_col], errors="coerce")
242
+ df = df.dropna(subset=[lat_col, lon_col])
243
+
244
+ if df.empty:
245
+ raise ValueError(
246
+ "⚠️[ValueError] 유효한 위경도 값이 없어 Shapefile을 생성할 수 없습니다."
247
+ )
248
+
249
+ geometry = gpd.points_from_xy(x=df[lon_col], y=df[lat_col])
250
+ gdf = gpd.GeoDataFrame(df, geometry=geometry, crs=target_crs)
251
+ else:
252
+ # GeoDataFrame의 CRS 처리: 존재하면 유지, 없으면만 설정
253
+ if gdf.crs is None:
254
+ gdf = gdf.set_crs(target_crs)
255
+
256
+ # 디렉터리 생성 보장
257
+ dir_name = os.path.dirname(path)
258
+ if dir_name:
259
+ os.makedirs(dir_name, exist_ok=True)
260
+
261
+ # 확장자에 따라 드라이버 선택
262
+ path_lower = path.lower()
263
+ if path_lower.endswith(".gpkg"):
264
+ driver = "GPKG"
265
+ file_format = "GeoPackage"
266
+ elif path_lower.endswith(".shp"):
267
+ driver = "ESRI Shapefile"
268
+ file_format = "Shapefile"
269
+ else:
270
+ # 확장자 없으면 .shp로 저장
271
+ path = f"{path}.shp"
272
+ driver = "ESRI Shapefile"
273
+ file_format = "Shapefile"
274
+
275
+ # 저장 (경고 메시지 억제)
276
+ with warnings.catch_warnings():
277
+ warnings.filterwarnings("ignore", category=RuntimeWarning)
278
+ gdf.to_file(path, driver=driver, encoding="utf-8")
279
+ print(f"✅ {file_format} 저장 완료: {path} (CRS: {target_crs.to_string()})")
@@ -455,7 +455,12 @@ def hs_regplot(
455
455
  fig, ax = __get_default_ax(width, height, 1, 1, dpi)
456
456
  outparams = True
457
457
 
458
- sb.regplot(data=df, x=xname, y=yname, color=palette, ax=ax, **params)
458
+ sb.regplot(data=df, x=xname, y=yname, scatter_kws={"color": palette},
459
+ line_kws={
460
+ "color": "red",
461
+ "linestyle": "--",
462
+ "linewidth": 2
463
+ }, ax=ax, **params)
459
464
  ax.grid()
460
465
 
461
466
  _finalize_plot(ax, callback, outparams)
@@ -3,7 +3,7 @@
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  import numpy as np
6
- from pandas import DataFrame, DatetimeIndex
6
+ from pandas import DataFrame, DatetimeIndex, read_csv, read_excel
7
7
  from scipy.stats import normaltest
8
8
  from tabulate import tabulate
9
9
 
@@ -171,5 +171,13 @@ def hs_load_data(key: str,
171
171
  DataFrame: 전처리(인덱스 설정, 카테고리 변환)가 완료된 데이터프레임
172
172
  """
173
173
 
174
- origin = load_data(key, local)
174
+ k = key.lower()
175
+
176
+ if k.endswith(".xlsx"):
177
+ origin = read_excel(key)
178
+ elif k.endswith(".csv"):
179
+ origin = read_csv(key)
180
+ else:
181
+ origin = load_data(key, local)
182
+
175
183
  return __data_info(origin, index_col, timeindex, info, categories)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hossam
3
- Version: 0.3.0
3
+ Version: 0.3.4
4
4
  Summary: Hossam Data Helper
5
5
  Home-page: https://github.com/leekh4232/hossam-data
6
6
  Author: Lee Kwang-Ho
@@ -12,7 +12,7 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Topic :: Software Development :: Libraries
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3 :: Only
15
- Requires-Python: >=3.8
15
+ Requires-Python: >=3.11
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: tqdm
@@ -29,6 +29,7 @@ Requires-Dist: scikit-learn
29
29
  Requires-Dist: pingouin
30
30
  Requires-Dist: statannotations
31
31
  Requires-Dist: joblib
32
+ Requires-Dist: geopandas
32
33
  Dynamic: author
33
34
  Dynamic: author-email
34
35
  Dynamic: classifier
@@ -124,6 +125,33 @@ from hossam import load_data, load_info
124
125
  datasets = load_info()
125
126
  print(datasets)
126
127
 
128
+ ## 🧭 문서(호스팅)
129
+
130
+ - 실시간 문서: https://py.hossam.kr
131
+ - 문서 생성 방식: MkDocs(Material) + mkdocstrings로 코드에서 API 레퍼런스를 자동 생성합니다.
132
+ - 로컬 미리보기:
133
+
134
+ ```bash
135
+ pip install mkdocs mkdocs-material "mkdocstrings[python]" pymdown-extensions mkdocs-autorefs
136
+ mkdocs serve
137
+ ```
138
+
139
+ - 정적 사이트 빌드:
140
+
141
+ ```bash
142
+ mkdocs build
143
+ ```
144
+
145
+ - 배포(깃허브 페이지 + 커스텀 도메인):
146
+
147
+ ```bash
148
+ mkdocs gh-deploy --clean --cname py.hossam.kr
149
+ ```
150
+
151
+ - 구성 파일과 문서 위치:
152
+ - MkDocs 설정: [mkdocs.yml](mkdocs.yml)
153
+ - 문서 소스: [docs/](docs)
154
+
127
155
  # 특정 키워드로 검색
128
156
  ad_datasets = load_info(search="AD")
129
157
 
@@ -7,6 +7,7 @@ setup.py
7
7
  hossam/__init__.py
8
8
  hossam/analysis.py
9
9
  hossam/data_loader.py
10
+ hossam/gis.py
10
11
  hossam/plot.py
11
12
  hossam/prep.py
12
13
  hossam/util.py
@@ -12,3 +12,4 @@ scikit-learn
12
12
  pingouin
13
13
  statannotations
14
14
  joblib
15
+ geopandas
@@ -2,14 +2,14 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="hossam",
5
- version="0.3.0",
5
+ version="0.3.4",
6
6
  description="Hossam Data Helper",
7
7
  author="Lee Kwang-Ho",
8
8
  author_email="leekh4232@gmail.com",
9
9
  license="MIT",
10
10
  packages=find_packages(exclude=[]),
11
11
  keywords=["data", "analysis", "helper", "hossam", "tensorflow", "이광호"],
12
- python_requires=">=3.8",
12
+ python_requires=">=3.11",
13
13
  zip_safe=False,
14
14
  url="https://github.com/leekh4232/hossam-data",
15
15
  install_requires=[
@@ -26,7 +26,8 @@ setup(
26
26
  "scikit-learn",
27
27
  "pingouin",
28
28
  "statannotations",
29
- "joblib"
29
+ "joblib",
30
+ "geopandas"
30
31
  ],
31
32
  include_package_data=True,
32
33
  long_description=open('README.md', encoding='utf-8').read() if __name__ == '__main__' else '',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes