Repository: hidrokit/hidrokit (language: python)

Issue:
bug: AssertionError: (None, <Day>) pada uji `test__dataframe_table`
### Sistem
* Versi hidrokit: 0.3.7
* Distribusi python: 3.6.13
* Sistem operasi: Windows

### Ringkasan Permasalahan

Pada saat testing, hasil dari uji `test_prep_excel.test__dataframe_table` menghasilkan error `AssertionError: (None, <Day>)`. Berdasarkan `pandas-dev/pandas#35446`, kemungkinan error terjadi saat _parsing_ tanggal.

### Error
```python
./tests/test_prep_excel.py::test__dataframe_table Failed: [undefined]AssertionError: (None, <Day>)
def test__dataframe_table():
        filepath = 'tests/data/excel/2013 DUGAAIR.xls'
        pivot = excel._file_single_pivot(filepath, template='pdderi')
        dataframe = excel._dataframe_table(pivot, 2013, name='dugaair')
    
        result = pd.read_csv('tests/data/2013 DUGAAIR.csv',
                             index_col=0, parse_dates=True)
        assert_frame_equal(
>           result, dataframe
        )
E       AssertionError: (None, <Day>)
```

### Solusi

solusi sementara menambah argumen `check_like=True` pada fungsi `assert_frame_equal(...)`

buat modul yang merekap fungsi analisis frekuensi
### Permintaan fitur/ide

buat modul yang merekap fungsi analisis frekuensi

### Alasan/Sumber

karena ke empat distribusi akan sering digunakan, akan lebih baik jika tidak mengingat kode isu untuk melakukan import. 

Fitur #151 (Uji Outlier)
### Ringkasan _Pull Request_

Penambahan modul baru `.contrib.taruma.hk151` terkait Uji Outlier. 

### Perbaikan/Fitur

close #151 

### Penggunaan

[manual](https://gist.github.com/taruma/7bf2e4e1601ab8390d9919043eb87682)


bug: hk102 tidak tersedia
### Sistem
* Versi hidrokit: 0.3.5

### Ringkasan Permasalahan

`hidrokit.contrib.taruma.hk102` pada isu #102 tidak tersedia pada versi ini (kelupaan). hehe.
buat modul yang merekap fungsi analisis frekuensi
### Permintaan fitur/ide

buat modul yang merekap fungsi analisis frekuensi

### Alasan/Sumber

karena ke empat distribusi akan sering digunakan, akan lebih baik jika tidak mengingat kode isu untuk melakukan import. 

bug: verbose mode `contrib.taruma.hk98.summary_all`
### Permintaan fitur/ide

Menghilangkan tampilan `PROCESSING: ...`

### Alasan/Sumber

Kalau kolomnya banyak, cukup mengganggu.
request: anfrek: Log Normal 2 Parameter
### Permintaan fitur/ide

anfrek: Log Normal 2 Parameter

### Alasan/Sumber

-
buat fungsi CDF untuk masing-masing anfrek
### Permintaan fitur/ide

Menghitung besar nilai "CDF" atau besaran probabilitas jika diketahui besarnya _faktor frekuensi_. 

Ini terkait #127 #172 #124 #126 

### Alasan/Sumber

Fungsi ini terkait #140 (uji kolmogorov-smirnov) karena dibutuhkan nilai probabilitas untuk masing-masing distribusi. Untuk normal dan log normal bisa menggunakan fungsi CDF dari scipy. Sedangkan untuk gumbel dan pearson, diberi alternatif menggunakan hitungan scipy atau tabel. 

### Checklist

- [x] Distribusi Normal
- [x] Distribusi Log Normal
- [x] Distribusi Log Pearson 3
- [x] Distribusi Gumbel
request: Uji Smirnov Kolmogorof
### Permintaan fitur/ide

Uji Smirnov Kolmogorof

### Alasan/Sumber

- Limantara, L. (2018): Rekayasa Hidrologi.
Impor data harian dari excel tanpa memperoleh nama sheet
### Permintaan fitur/ide

Pada modul `.contrib.taruma.hk88` terkait membaca data harian menggunakan template excel yang disiapkan, masih diperlukan input berupa nama stasiunnya. Hal ini bisa dipermudah dengan membaca seluruh _sheet_ yang tidak diawali dengan `_`. Hal ini membuat lebih praktis digunakan saja. 

Fitur ini bisa dikembangkan dari modul `.contrib.taruma.hk88` sebagai modul baru atau memperbarui modul `.hk88`. 

### Alasan/Sumber

- #88 

perbarui changelog
Perbarui changelog


Discussion / hints:

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index 35e540e..a5d84d9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,6 @@ cache: pip
 python:
   - "3.7"
   - "3.6"
-  - "3.5"
 
 # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
 install: 
diff --git a/.zenodo.json b/.zenodo.json
index 47669fd..7ce7cbe 100644
--- a/.zenodo.json
+++ b/.zenodo.json
@@ -1,12 +1,18 @@
 {
     "creators": [
         {
-            "name": "Megariansyah, Taruma"
+            "name": "Megariansyah, Taruma",
+            "orcid": "0000-0002-1551-7673"
         }
     ],
     "description": "hidrokit adalah proyek open source paket python yang dapat digunakan untuk membantu proses analisis hidrologi berupa pengolahan data, analisis data, dan visualisasi data.",
+    "keywords": [
+        "analisis",
+        "hidrologi",
+        "python"
+    ],
     "license": {
         "id": "MIT"
     },
-    "title": "hidrokit: analisis hidrologi dengan python"
+    "title": "hidrokit"
 }
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be4304b..cd7d9a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,91 +1,171 @@
 # Changelog
 Seluruh perubahan penting pada proyek ini akan didokumentasikan pada bilah ini. Dokumentasi ini hanya untuk perubahan pada paket python.
 
-Changelog ini sudah kadaluarsa dan belum diperbarui lagi.
-
 Penulisan diadaptasi dari [Keep a Changelog](https://keepachangelog.com/id-ID/1.0.0/), dan proyek ini menggunakan penomoran [versi Semantik](https://semver.org/lang/id/spec/v2.0.0.html). 
 
 ---
 
 ## Belum Rilis
 
+**Added/Ditambahkan**
+- Penambahan modul `.contrib.taruma`:
+  - #102 `hk102`: upsampling dataset.
+  - #151 `hk151`: uji outlier.
+  - #158 `hk158`: perhitungan parameter statistik.
+  - Modul Uji Kecocokan Distribusi:
+    - #140 `hk140`: uji kolmogorov-smirnov.
+    - #141 `hk141`: uji chi-square.
+  - Modul Analisis Frekuensi:
+    - #124 `hk124`: distribusi log normal 2 parameter.
+    - #126 `hk126`: distribusi log pearson tipe III.
+    - #127 `hk127`: distribusi gumbel.
+    - #172 `hk172`: distribusi normal.
+  - #179 Fungsi CDF `.calc_prob()` untuk setiap analisis frekuensi.
+  - #194 `anfrek`: modul kumpulan fungsi analisis frekuensi.
+
+**Fixed/Diperbaiki**
+- #169 Perbaikan fungsi test `test_prep_excel.test__dataframe_table()`.
+- #115 Penambahan argumen `verbose=False` pada modul `.contrib.taruma.hk98`.
+- #162 Pembaruan fungsi `.contrib.taruma.hk88.read_workbook()` untuk membaca seluruh _sheet_ tanpa memasukan nama stasiun/_sheet_. 
+
+**Changed/Diubah**
+- #162 Luaran argumen `as_df=False` pada fungsi `.contrib.taruma.hk88.read_workbook()` berubah dari luaran berupa `list` menjadi `dictionary`. 
+
 ---
 
 ## Telah Rilis
-### 11 Juli 2019 - v0.2.0
-Ini merupakan perubahan sangat signifikan dari versi sebelumnya dan dimulainya penggunaan layanan integrasi dan _code review_. Perubahan nama fungsi dan modul sangat signifikan sehingga fungsi/metode yang digunakan sebelumnya tidak berfungsi sama sekali (***no backward-compatibility***). Rilis ini disertakan dengan dirilisnya website untuk [hidrokit] dan [hidrokit-nb]. 
 
-**Baru**
+### 2020-07-13 - 0.3.6
+
+**Fixed/Diperbaiki**
+- Perbaikan argumen `keep_first=False` pada fungsi `timeseries.timestep_table()`.
+
+### 2020-01-15 - 0.3.5
+
+**Added/Ditambahkan**
+- Penambahan modul `.contrib.taruma`:
+  - `hk87`: perhitungan debit andalan menggunakan kurva durasi debit
+  - `hk88`: ambil dataset hujan harian dari excel
+  - `hk89`: pemodelan NRECA
+  - `hk90`: kalibrasi model (NRECA/FJMOCK)
+  - `hk99`: perhitungan curah hujan dengan metode poligon thiessen
+  - `hk98`: rekap dataset deret waktu
+  - `hk96`: pemodelan F.J. Mock
+  - `hk102`: upsampling dataset
+  - `hk106`: perhitungan evapotranspirasi
+
+### 2019-12-09 - 0.3.4
+
+**Added/Ditambahkan**
+- Penambahan modul `.contrib.taruma`:
+  - `hk84`: meringkas informasi data hujan jam-jaman
+
+### 2019-11-30 - 0.3.3
+
+**Added/Ditambahkan**
+- Penambahan modul `.contrib.taruma`:
+  - `hk79`: mengekstrak informasi data jam-jaman dari excel
+- Penambahan `makefile` untuk memudahkan publikasi
+
+**Changed/Diubah**
+- Memperbaiki file sitasi (.cff)
+- Memperbaiki pemanggilan `hidrokit.__version__`
+- Memperbaiki _lint_, yaitu baris yang terlalu panjang
+- 
+
+### 2019-10-15 - 0.3.2
+
+**Added/Ditambahkan**
+- Penambahan subpaket `.contrib.taruma` dengan modul sebagai berikut:
+  - `.hk43`: ubah pivot table ke dataframe
+  - `.hk53`: buat table/tensor untuk pemodelan deep learning LSTM
+  - `.hk73`: mengolah berkas dari bmkg
+- Penambahan subpaket `.contrib` yang digunakan untuk tempat khusus kontributor tanpa pengecekan CI.
+- Penambahan file .cff untuk sitasi dan pembacaan oleh zenodo.
+
+**Changed/Diubah**
+- Perubahan penulisan _tag_ yang berawal dari "v0.3.2" menjadi "0.3.2". Terkait pembacaan otomatis oleh Zenodo.
+
+---
+### 2019-07-26 - v0.2.1
+
+**Changed/Diubah**
+- Added/Ditambahkan argumen `template=` pada fungsi `prep.timeseries.timestep_table`
+- Changed/Diubah penggunaan f-string menjadi `.format` terkait kompatibilitas dengan python 3.5
+- Changed/Diubah konfigurasi codecov.
+- Changed/Diubah konfigurasi travis-ci. Added/Ditambahkan pemeriksaan dengan python 3.5.  
+
+
+### 2019-07-11 - v0.2.0
+
+**Added/Ditambahkan**
 - Penamaan modul dan fungsi yang lebih mudah dibaca dan diingat. 
 - Paket dibagi menjadi tiga bagian utama yaitu persiapan (`prep`), analisis (`analysis`), dan visualisasi (`viz`). Penamaan modul dan fungsi juga diubah dan disesuaikan dengan standar agar lebih jelas dan mudah diingat.
   - Modul untuk membaca data dan manipulasi data seperti `dlkit`, `datakit`, `prepkit` akan dibawah modul `prep` dan digantikan namanya menjadi `timeseries`, `read`, `excel`.
   - Modul untuk menampilkan data seperti `viewkit` dan fungsi plot dari `dlkit` akan dibawah modul `viz` dan berganti nama menjadi `table` dan `graph`. 
   - Beberapa nama fungsi juga berubah menjadi lebih jelas seperti `datakit.dict_null_data` menjadi `prep.read.missing_row()`. 
-- Integrasi [travis-ci], [codecov], [codacy].
+- Integrasi travis-ci, codecov, codacy.
 - Testing menggunakan pytest dengan target 90% _coverage_.
 - Rilisnya website [hidrokit] dan [hidrokit-nb] yang telah disertai panduan berkontribusi dan mengenai proyek pada umumnya. 
 
-**Perbaikan**
+**Fixed/Diperbaiki**
 - Dokumentasi [hidrokit.readthedocs.io] diperbaiki dengan membagi menjadi tiga halaman yaitu data preparation, data analysis, dan data visualization.
 
-**Penghilangan**
+**Removed/Dihilangkan**
 - Modul `bmkgkit` dihapuskan dan belum ada penggantinya.
 - Modul berikut ini telah dihapus/digantikan sehingga tidak akan digunakan lagi:
   - `dlkit`, `viewkit`, `bmkgkit`, `prepkit`, `datakit`.
 
-[travis-ci]: https://travis-ci.com
-[codecov]: https://codecov.io/
-[codacy]: https://www.codacy.com/
-
 ---
+### 2019-06-25 - v0.1.3
+
+**Added/Ditambahkan**
+- Situs dokumentasi teknis di [readthedocs](https://hidrokit.rtfd.io).
+- `docstring` untuk setiap metode.
+
+### 2019-06-10 - v0.1.2
+
+**Added/Ditambahkan**
+- modul `.datakit`.
+- fungsi `.dlkit.table_timesteps()`.
+- argumen baru pada fungsi `.dlkit.multi_column_timesteps()` untuk menyesuaikan fungsi `.dlkit.table_timesteps()`.
+
+**Dokumentasi - d0.1.2**
+- Changed/Diubah templat untuk isu dan PR.
+- tambah dokumen _changelog_.
+- memindahkan _notebook_ ke _repo_ [`hidrokit-nb`](https://github.com/taruma/hidrokit-nb).
+
+### 2019-05-14 - v0.1.1
+
+**Added/Ditambahkan**
+- modul `.dlkit`.
+
+### 2019-01-09 - v0.1.0
+
+**Added/Ditambahkan**
+- Mengubah `hidrokit` sebagai paket python.
+- Distribusi `hidrokit` melalui PyPI.
+
+**Dokumentasi**
+- Changed/Diubah berkas README.
+- Templat isu
+- Kode etik menggunakan _Contributor Covenant_ berbahasa Indonesia.
+- Mengacu halaman wiki untuk panduan kontribusi.
+
+### 2019-01-05 - v0.0.0
+
+**Added/Ditambahkan**
+- `hidrokit` sebagai _module_.
+- Dokumentasi proyek (readme, contributing, etc.).
+- _notebook_ penggunaan `hidrokit`.
+
+### 2018-10-20 - n/a
 
-### 25 Juni 2019 - v0.1.3
-Versi ini fokus ke dokumentasi teknis. Dari versi ini, hidrokit memiliki dokumentasi teknis otomatis yang dibuat menggunakan sphinx dan readthedocs. Situsnya adalah [hidrokit.readthedocs.io].
-- **Penambahan**
-  - Situs dokumentasi teknis di [readthedocs](https://hidrokit.rtfd.io).
-  - `docstring` untuk setiap metode.
-
-### 10 Juni 2019 - v0.1.2
-Modul baru `.datakit` digunakan untuk analisis dan penambahan fungsi `.dlkit.table_timesteps()` untuk mebangkitkan tabel _timesteps_ langsung dari `pandas.DataFrame`. Perubahan signifikan untuk dokumentasi non-teknis. Dokumentasi dapat diakses melalui halaman [hidrokit] atau [hidrokit-nb].
-- **Penambahan**
-  - modul `.datakit`.
-  - fungsi `.dlkit.table_timesteps()`.
-  - argumen baru pada fungsi `.dlkit.multi_column_timesteps()` untuk menyesuaikan fungsi `.dlkit.table_timesteps()`.
-- **Dokumentasi - d0.1.2**
-  - perubahan templat untuk isu dan PR.
-  - tambah dokumen _changelog_.
-  - memindahkan _notebook_ ke _repo_ [`hidrokit-nb`](https://github.com/taruma/hidrokit-nb).
-
-### 14 Mei 2019 - v0.1.1
-Ditambahnya modul baru bernama `dlkit` untuk membantu proses _deep learning_ pada permasalahan _time series_. Modul ini digunakan dalam membuat tabel _timesteps_. 
-- **Penambahan**
-  - modul `.dlkit`.
-
-### 9 Januari 2019 - v0.1.0
-Mengikuti standar distribusi paket Python (PyPI). Memperbaiki dokumentasi komunitas (README, Kode etik). Mengalihkan dokumentasi non-teknis ke halaman wiki.
-- **Penambahan**
-  - Mengubah `hidrokit` sebagai paket python.
-  - Distribusi `hidrokit` melalui PyPI.
-- **Dokumentasi**
-  - Perubahan berkas README.
-  - Templat isu
-  - Kode etik menggunakan _Contributor Covenant_ berbahasa Indonesia.
-  - Mengacu halaman wiki untuk panduan kontribusi.
-
-### 5 Januari 2019 - v0.0.0
-Memulai menandai pengembangan awal `hidrokit` yang masih berupa _module_ python. 
-- **Penambahan**
-  - `hidrokit` sebagai _module_.
-  - Dokumentasi proyek (readme, contributing, etc.).
-  - _notebook_ penggunaan `hidrokit`.
-
-### 20 Oktober 2018 - n/a
-Dibuatnya _repository_ `hidrokit`.
-- **Penambahan**
-  - Membuat _repository_ di GitHub.
+**Added/Ditambahkan**
+- Membuat _repository_ di GitHub.
 
 ---
 
-[hidrokit]: https://taruma.github.io/hidrokit
-[hidrokit-nb]: https://taruma.github.io/hidrokit-nb
-[hidrokit.readthedocs.io]: https://hidrokit.rtfd.io
+[hidrokit]: https://hidrokit.github.io/hidrokit
+[hidrokit-nb]: https://hidrokit.github.io/notebook
+[hidrokit.readthedocs.io]: https://hidrokit.rtfd.io
\ No newline at end of file
diff --git a/CITATION.cff b/CITATION.cff
index 550bb8c..298ad9c 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -1,14 +1,14 @@
 --- 
+cff-version: 1.2.0
+message: "Jika menggunakan perangkat lunak ini, mohon sitasi menggunakan metadata ini."
 abstract: "hidrokit adalah proyek open source paket python yang dapat digunakan untuk membantu proses analisis hidrologi berupa pengolahan data, analisis data, dan visualisasi data."
 authors: 
-  - 
-    family-names: Megariansyah
-    given-names: Taruma
-cff-version: "1.0.3"
-date-released: 2020-01-15
+  - family-names: "Megariansyah"
+    given-names: "Taruma"
+    orcid: "https://orcid.org/0000-0002-1551-7673"
+title: "hidrokit: Analisis Hidrologi dengan Python"
+date-released: 2022-04-05
+version: "0.3.7"
 doi: 10.5281/zenodo.3276220
 license: MIT
-message: "Jika menggunakan perangkat lunak ini, mohon sitasi menggunakan metadata ini."
 repository-code: "https://github.com/hidrokit/hidrokit"
-title: "hidrokit: Analisis Hidrologi dengan Python"
-version: "0.3.6"
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 922d259..e6c5dbe 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -32,7 +32,7 @@ Kode Etik ini berlaku baik di dalam proyek maupun di ruang publik ketika seseora
 
 ## Penegakan
 
-Kejadian perilaku kasar, pelecehan, atau tidak dapat diterima yang terjadi dapat dilaporkan kepada tim inti proyek di timhidrokit@gmail.com. Semua keluhan akan ditinjau dan diselidiki dan akan menghasilkan tanggapan yang dianggap perlu dan sesuai dengan keadaan. Tim inti proyek berkewajiban menjaga kerahasiaan pelapor berkenaan dengan pelapor suatu insiden. 
+Kejadian perilaku kasar, pelecehan, atau tidak dapat diterima yang terjadi dapat dilaporkan kepada tim inti proyek di hi@taruma.info. Semua keluhan akan ditinjau dan diselidiki dan akan menghasilkan tanggapan yang dianggap perlu dan sesuai dengan keadaan. Tim inti proyek berkewajiban menjaga kerahasiaan pelapor berkenaan dengan pelapor suatu insiden. 
 
 Rincian lebih lanjut tentang kebijakan penegakan khusus akan dipublikasikan secara terpisah. Pengelola proyek yang tidak mengikuti atau melaksanakan Kode Etik dengan itikad baik mungkin akan menghadapi dampak sementara atau permanen seperti yang ditentukan oleh para anggota lain dari pimpinan proyek.
 
diff --git a/LICENSE.txt b/LICENSE.txt
index 9409b60..097b0c8 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,7 +1,6 @@
 MIT License
 
-Copyright (c) 2018-2020, Taruma Sakti Megariansyah
-Copyright (c) 2020, Hidrokit contributors.
+Copyright (c) 2018-2022, Taruma Sakti Megariansyah
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 3159f5a..ed5f849 100644
--- a/README.md
+++ b/README.md
@@ -5,25 +5,15 @@
 ![PyPI - Status](https://img.shields.io/pypi/status/hidrokit.svg)
 ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/hidrokit.svg)
 [![GitHub license](https://img.shields.io/github/license/hidrokit/hidrokit.svg)](https://github.com/hidrokit/hidrokit/blob/master/LICENSE)
-![GitHub tag (latest by date)](https://img.shields.io/github/tag-date/hidrokit/hidrokit.svg?label=recent%20version)
 [![DOI](https://zenodo.org/badge/145389179.svg)](https://zenodo.org/badge/latestdoi/145389179)
 
+[![Sponsored by PT. FIAKO Enjiniring Indonesia](https://img.shields.io/badge/sponsored%20by-PT.%20FIAKO%20Enjiniring%20Indonesia-blue.svg)](http://www.fiako.co.id/)
+
 <a href="https://hidrokit.github.io/hidrokit"><b>Kunjungi situs resmi hidrokit.</b></a>
 </div>
 
 `hidrokit` adalah proyek _open source_ paket *python* yang dapat digunakan untuk membantu proses analisis hidrologi dimulai dari pengolahan data, analisis data, dan visualisasi data. [Baca lebih lanjut mengenai hidrokit](https://hidrokit.github.io/hidrokit/tentang-hidrokit).
 
-## PENGUMUMAN
-
-<div align="center">
-
-**Versi 0.3.5 merupakan versi terakhir hidrokit. Pengembangan hidrokit dalam kondisi hiatus dikarenakan tidak memiliki pengelola. Untuk informasi lebih lanjut baca [tulisan ini](https://medium.com/@taruma/hidrokit-pada-tahun-2020-c00cd8860c0e).**
-
-
-**_Repository hidrokit dialihkan ke hidrokit/hidrokit dari taruma/hidrokit di github._**
-
-</div>
-
 ## Release
 
 <table>
@@ -48,37 +38,6 @@
 
 </table>
 
-
-## Status
-
-<table>
-  <tr align="center">
-    <th>Branch</th>
-    <th>master</th>
-    <th>gh-pages</th>
-  </tr>
-  <tr>
-    <td><b>Services</b></td>
-    <td></td>
-    <td></td>
-  </tr>
-  <tr>
-    <td>Travis-ci</td>
-    <td><img alt="master" src="https://img.shields.io/travis/hidrokit/hidrokit/master.svg?label=build&logo=travis"></td>
-    <td><img alt="gh-pages" src="https://img.shields.io/travis/hidrokit/hidrokit/gh-pages.svg?label=build&logo=travis"></td>
-  </tr>
-  <tr>
-    <td>Codecov</td>
-    <td><img alt="master" src="https://img.shields.io/codecov/c/github/hidrokit/hidrokit/master.svg?logo=codecov"></td>
-    <td></td>
-  </tr>
-  <tr>
-    <td>Codacy</td>
-    <td><img alt="master" src="https://img.shields.io/codacy/grade/63d10854d6d14c79b6162cae547dc594/master.svg?logo=codacy"></td>
-    <td></td>
-  </tr>
-</table>
-
 ## Memulai
 
 Untuk memudahkan penggunaan, disarankan menggunakan **Anaconda3** sebagai distribusi *python*. Download **Anaconda3** [disini](https://www.anaconda.com/download/). Baca panduan di halaman [instalasi untuk lebih detail](https://hidrokit.github.io/hidrokit/panduan/instalasi).
@@ -92,7 +51,7 @@ pip install hidrokit
 ```
 *(akses internet diperlukan saat melakukan pemasangan)*
 
-### Versi terbaru (_unstable_/_latest_)
+### Versi terbaru (_latest_)
 
 Untuk versi paling terbaru bisa melakukan pemasangan berdasarkan cabang _latest_.
 ```bash
@@ -100,7 +59,7 @@ pip install git+https://github.com/hidrokit/hidrokit.git@latest
 ```
 
 ## Catatan penting
-- hidrokit hanya mendukung python versi 3.5 ke atas.
+- Sejak versi 0.3.7, hidrokit hanya mendukung python versi 3.6 ke atas.
 - Pemasangan `xlrd` dibutuhkan jika menggunakan module `excel` untuk membaca bilah _Excel_.
 - Versi 0.2.x tidak memiliki *backward-compatibility* dengan versi 0.1.x.
 - Baca halaman [pemasangan untuk lebih detail](https://hidrokit.github.io/hidrokit/panduan/instalasi).
@@ -111,19 +70,26 @@ Untuk memulai penggunaan, gunakan perintah `import`. Contoh:
 
 ```python
 from hidrokit.prep import read
-from hidrokit.prep.read import missing_row
-from hidrokit.viz import graph
+
+# untuk penggunaan subpaket .contrib
+# from hidrokit.contrib.[nama user] import [modul]
+from hidrokit.contrib.taruma import anfrek
+from hidrokit.contrib.taruma import hk88, hk102
 ```
 
 Untuk penggunaan baca halaman [penggunaan](https://hidrokit.github.io/hidrokit/panduan/penggunaan).
 
-## Hidrokit Notebook
+### Laporan Implementasi
 
-Kumpulan contoh _notebook_ dapat diakses di halaman [Hidrokit Notebook](https://hidrokit.github.io/notebook/).
+Penggunaan hidrokit juga bisa dilihat dari [_Laporan Implementasi_](https://taruma.github.io/vivaldi/laporan-implementasi). Berikut daftar laporan implementasi yang telah dibuat:
+
+no laporan | tanggal | versi hidrokit | judul | lihat (nbviewer) | pdf
+:- | :- | :- | :- | :- | :-
+**LI-04** | 11 Februari 2020 | `0.3.5` | Perbandingan Model Variasi _Recurrent Neural Networks_ Pada Kasus Prediksi Debit Aliran | [nbviewer](https://nbviewer.org/gist/taruma/9d1ef5c6d629c792bed0c3f68b324675) | [pdf](https://1drv.ms/b/s!AmxSTa4UunElhoVm7i0EuKdPkPlzVg?e=rjXNpf) 
+**LI-03** | 16 Januari 2020 | `0.3.5` | Analisis Hidrologi Menggunakan hidrokit | [nbviewer](https://nbviewer.jupyter.org/gist/taruma/4c1ed1212290965ecda056f45d7aaea2) | [pdf](https://1drv.ms/b/s!AmxSTa4UunElhoU3ehyoy45_RG6hjA?e=5wUb8d)
+**LI-02** | 22 Oktober 2019 | `0.3.2` | Prediksi Debit Aliran Menggunakan Long Short-Term Memory (LSTM) | [nbviewer](https://nbviewer.jupyter.org/gist/taruma/8186dba212875f6b3f1677a5e2f9a70f) | [pdf](https://1drv.ms/b/s!AmxSTa4UunElhoU1sISX0gc4BammwQ?e=MGHcwT)
+**LI-01** | 13 Juli 2019 | `0.2.0` | Prediksi Kualitas Air Menggunakan Artificial Neural Networks | [nbviewer](https://nbviewer.jupyter.org/gist/taruma/12bf06ab7307340525eecf5b3c8beb9c) | [pdf](https://1drv.ms/b/s!AmxSTa4UunElhoU27FZ3pMHVvWeMsA?e=ouC2KK)
 
-<div align="center">
-<a href="https://hidrokit.github.io/notebook"><img src="https://hidrokit.github.io/notebook/assets/images/hidrokit-nb-800x200.jpg" width="50%"></a>
-</div>
 
 ## Untuk Kontributor
 
@@ -132,3 +98,7 @@ Tertarik menjadi kontributor? Baca [**berkontribusi**](https://hidrokit.github.i
 ## Lisensi
 
 Paket hidrokit menggunakan [lisensi MIT](LICENSE.txt). Dokumentasi yang disertai pada proyek ini menggunakan lisensi [Creative Commons Attribution 4.0 International (CC-BY-4.0)](https://creativecommons.org/licenses/by/4.0/deed.id). 
+## Acknowledgement
+
+- Terima kasih untuk [PT. FIAKO Enjiniring Indonesia](http://www.fiako.co.id/) (FIAKO ENGINEERING) yang telah mensponsori proyek ini sejak Februari 2022.
+- Terima kasih untuk **LKO** yang telah mensponsori proyek ini sejak versi 0.2.x hingga 0.3.6. 
\ No newline at end of file
diff --git a/environment-dev.yml b/environment-dev.yml
index e3cb565..62d15ba 100644
--- a/environment-dev.yml
+++ b/environment-dev.yml
@@ -19,4 +19,5 @@ dependencies:
   - sphinx # sphinx settings
   - numpydoc
   - pip # pip installation
+  - scipy
   
diff --git a/hidrokit/__version__.py b/hidrokit/__version__.py
index 27e660b..2953166 100644
--- a/hidrokit/__version__.py
+++ b/hidrokit/__version__.py
@@ -1,4 +1,8 @@
-VERSION = (0, 3, 6)
-DEV_STATUS = ""
+"""
+hidrokit version
+"""
+
+VERSION = (0, 3, 7)
+DEV_STATUS = "-rc.0"
 
 __version__ = '.'.join(map(str, VERSION)) + DEV_STATUS
diff --git a/hidrokit/contrib/taruma/__init__.py b/hidrokit/contrib/taruma/__init__.py
index 722ff75..d7a31a7 100644
--- a/hidrokit/contrib/taruma/__init__.py
+++ b/hidrokit/contrib/taruma/__init__.py
@@ -1,6 +1,45 @@
-__all__ = (
-    ['hk43', 'hk53', 'hk73', 'hk79'] +
-    ['hk84', 'hk87', 'hk88', 'hk89'] +
-    ['hk90', 'hk96', 'hk98', 'hk99'] +
-    ['hk106']
-)
+
+"""
+0.3.2
+hk43 | Pivot Table
+hk53 | Tensor Input RNN/LSTM
+hk73 | BMKG
+
+0.3.3
+hk79 | Excel Jam-Jaman
+
+0.3.4
+hk84 | Ringkasan Jam-Jaman
+
+0.3.5
+hk87 | Perhitungan Debit Andalan Menggunakan Kurva Durasi Debit
+hk88 | Baca Dataset Harian
+hk89 | Model NRECA
+hk90 | Kalibrasi Model
+hk96 | Model FJMock
+hk98 | Rekap Deret Waktu
+hk99 | Poligon Thiessen
+hk106 | Evapotranspirasi
+
+0.3.7
+hk102 | Upsampling (kelupaan)
+hk124 | Anfrek: Log Normal
+hk126 | Anfrek: Log Pearson 3
+hk127 | Anfrek: Gumbel
+hk140 | Uji Kolmogorov-Smirnov
+hk141 | Uji Chi-Square
+hk151 | Uji Outlier
+hk158 | Parameter Statistik
+hk172 | Anfrek: Normal
+
+anfrek | analisis frekuensi (normal, lognormal, logpearson3, gumbel)
+"""
+
+__all__ = ([
+    'hk43', 'hk53', 'hk73', 'hk79', 'hk84',
+    'hk87', 'hk88', 'hk89', 'hk90', 'hk96',
+    'hk98', 'hk99', 'hk102', 'hk106',
+    'hk124', 'hk126', 'hk127', 'hk140', 'hk141',
+    'hk151', 'hk158', 'hk172',
+    'anfrek',
+])
diff --git a/hidrokit/contrib/taruma/anfrek.py b/hidrokit/contrib/taruma/anfrek.py
new file mode 100644
index 0000000..b0fa21b
--- /dev/null
+++ b/hidrokit/contrib/taruma/anfrek.py
@@ -0,0 +1,15 @@
+"""
+Modul rekap analisis frekuensi. Untuk manual lihat modul terpisah.
+"""
+
+from hidrokit.contrib.taruma import hk172, hk124, hk126, hk127
+
+freq_normal = hk172.freq_normal
+freq_lognormal = hk124.freq_lognormal
+freq_logpearson3 = hk126.freq_logpearson3
+freq_gumbel = hk127.freq_gumbel
+
+normal = hk172
+lognormal = hk124
+logpearson3 = hk126
+gumbel = hk127
diff --git a/hidrokit/contrib/taruma/hk102.py b/hidrokit/contrib/taruma/hk102.py
new file mode 100644
index 0000000..377398e
--- /dev/null
+++ b/hidrokit/contrib/taruma/hk102.py
@@ -0,0 +1,23 @@
+"""manual:
+https://gist.github.com/taruma/96c321175ecac3e51350ef4c94f3d7d4"""
+
+import numpy as np
+import pandas as pd
+
+# ref: https://stackoverflow.com/q/29612705/4886384
+def upsampling(df, freq='D', fill_method='ffill', 
+               use_inter=False, inter_method='linear', inter_keys={}, 
+               reindex=False):
+    start = df.index.min() - pd.DateOffset(day=1)
+    end = df.index.max() - pd.DateOffset(day=31)
+    date = pd.date_range(start, end, freq=freq)
+
+    newdf = df.reindex(date)
+
+    if reindex:
+        return newdf
+
+    if use_inter:
+        return newdf.interpolate(method=inter_method, **inter_keys)
+    else:
+        return newdf.fillna(method=fill_method)
\ No newline at end of file
diff --git a/hidrokit/contrib/taruma/hk124.py b/hidrokit/contrib/taruma/hk124.py
new file mode 100644
index 0000000..446ba99
--- /dev/null
+++ b/hidrokit/contrib/taruma/hk124.py
@@ -0,0 +1,99 @@
+"""manual:
+https://gist.github.com/taruma/5d3ab88893e56f895dc3f36ea19c3e60"""
+
+import numpy as np
+import pandas as pd
+from scipy import stats
+
+# Tabel Nilai Variabel Reduksi Gauss
+# Dari buku hidrologi: Aplikasi Metode Statistik untuk Analisa Data. hal.119
+
+# KODE: SW
+
+_DATA_SW = [
+    [1.001, 0.999, -3.050],
+    [1.005, 0.995, -2.580],
+    [1.010, 0.990, -2.330],
+    [1.050, 0.950, -1.640],
+    [1.110, 0.900, -1.280],
+    [1.250, 0.800, -0.840],
+    [1.330, 0.750, -0.670],
+    [1.430, 0.700, -0.520],
+    [1.670, 0.600, -0.250],
+    [2.000, 0.500, 0.000],
+    [2.500, 0.400, 0.250],
+    [3.330, 0.300, 0.520],
+    [4.000, 0.250, 0.670],
+    [5.000, 0.200, 0.840],
+    [10.000, 0.100, 1.280],
+    [20.000, 0.050, 1.640],
+    [50.000, 0.200, 2.050],
+    [100.000, 0.010, 2.330],
+    [200.000, 0.005, 2.580],
+    [500.000, 0.002, 2.880],
+    [1000.000, 0.001, 3.090],
+]
+
+_COL_SW = ['periode_ulang', 'peluang', 'k']
+
+t_normal_sw = pd.DataFrame(
+    data=_DATA_SW, columns=_COL_SW
+)
+
+def _find_k_in_table(return_period, table):
+    x = table.periode_ulang
+    y = table.k
+    return np.interp(return_period, x, y)
+
+def find_K(return_period, source='scipy'):
+    if source.lower() == 'soewarno':
+        return _find_k_in_table(return_period, t_normal_sw)
+    elif source.lower() == 'scipy':
+        return_period = np.array(return_period)
+        return stats.norm.ppf(1 - 1/return_period)
+
+def calc_x_lognormal(x, return_period=[5], source='scipy', show_stat=False):
+    return_period = np.array(return_period)
+    y = np.log10(x)
+    y_mean = np.mean(y)
+    y_std = np.std(y, ddof=1)
+    n = len(y)
+
+    k = find_K(return_period, source=source)
+
+    if show_stat:
+        print(f'y_mean = {y_mean:.5f}')
+        print(f'y_std = {y_std:.5f}')
+        print(f'k = {k}')
+
+    val_y = y_mean + k * y_std
+    val_x = np.power(10, val_y)
+    return val_x
+
+def freq_lognormal(
+    df, col=None,
+    return_period=[2, 5, 10, 20, 25, 50, 100], show_stat=False, source='scipy',
+    col_name='Log Normal'):
+
+    col = df.columns[0] if col is None else col
+
+    x = df[col].copy()
+
+    arr = calc_x_lognormal(
+        x, return_period=return_period, show_stat=show_stat, source=source)
+
+    return pd.DataFrame(
+        data=arr, index=return_period, columns=[col_name]
+    )
+
+def _calc_prob_in_table(k, table):
+    x = table.k
+    y = table.peluang
+    return np.interp(k, x, y)
+
+def calc_prob(k, source='scipy'):
+    if source.lower() == 'soewarno':
+        k = np.array(k)
+        return 1 - _calc_prob_in_table(k, t_normal_sw)
+    elif source.lower() == 'scipy':
+        return stats.norm.cdf(k)
diff --git a/hidrokit/contrib/taruma/hk126.py b/hidrokit/contrib/taruma/hk126.py
new file mode 100644
index 0000000..de44c9d
--- /dev/null
+++ b/hidrokit/contrib/taruma/hk126.py
@@ -0,0 +1,319 @@
+"""manual:
+https://gist.github.com/taruma/60725ffca91dc6e741daee9a738a978b"""
+
+import numpy as np
+import pandas as pd
+from scipy import stats, interpolate
+
+# tabel dari Soewarno, hal. 219
+# lampiran tabel III-3, Nilai k Distribusi Pearson tipe III
+# dan Log Pearson ti
+
+# kode: SW
+
+_DATA_SW = np.array([
+    [-0.360, 0.420, 1.180, 2.278, 3.152, 4.051, 4.970, 7.250],
+    [-0.360, 0.518, 1.250, 2.262, 3.048, 3.845, 4.652, 6.600],
+    [-0.330, 0.574, 1.284, 2.240, 2.970, 3.705, 4.444, 6.200],
+    [-0.307, 0.609, 1.302, 2.219, 2.912, 3.605, 4.298, 5.910],
+    [-0.282, 0.643, 1.318, 2.193, 2.848, 3.499, 4.147, 5.660],
+    [-0.254, 0.675, 1.329, 2.163, 2.780, 3.388, 3.990, 5.390],
+    [-0.225, 0.705, 1.337, 2.128, 2.706, 3.271, 3.828, 5.110],
+    [-0.195, 0.732, 1.340, 2.087, 2.626, 3.149, 3.661, 4.820],
+    [-0.164, 0.758, 1.340, 2.043, 2.542, 3.022, 3.489, 4.540],
+    [-0.148, 0.769, 1.339, 2.018, 2.498, 2.957, 3.401, 4.395],
+    [-0.132, 0.780, 1.336, 1.998, 2.453, 2.891, 3.312, 4.250],
+    [-0.116, 0.790, 1.333, 1.967, 2.407, 2.824, 3.223, 4.105],
+    [0.099, 0.800, 1.328, 1.939, 2.359, 2.755, 3.132, 3.960],
+    [-0.083, 0.808, 1.323, 1.910, 2.311, 2.686, 3.041, 3.815],
+    [-0.066, 0.816, 1.317, 1.880, 2.261, 2.615, 2.949, 3.670],
+    [-0.050, 0.824, 1.309, 1.849, 2.211, 2.544, 2.856, 3.525],
+    [-0.033, 0.830, 1.301, 1.818, 2.159, 2.472, 2.763, 3.380],
+    [-0.017, 0.836, 1.292, 1.785, 2.107, 2.400, 2.670, 3.235],
+    [0.000, 0.842, 1.282, 1.751, 2.054, 2.326, 2.576, 3.090],
+    [0.017, 0.836, 1.270, 1.761, 2.000, 2.252, 2.482, 3.950],
+    [0.033, 0.850, 1.258, 1.680, 1.945, 2.178, 2.388, 2.810],
+    [0.050, 0.853, 1.245, 1.643, 1.890, 2.104, 2.294, 2.675],
+    [0.066, 0.855, 1.231, 1.606, 1.834, 2.029, 2.201, 2.540],
+    [0.083, 0.856, 1.216, 1.567, 1.777, 1.955, 2.108, 2.400],
+    [0.099, 0.857, 1.200, 1.528, 1.720, 1.880, 2.016, 2.275],
+    [0.116, 0.857, 1.183, 1.488, 1.663, 1.806, 1.926, 2.150],
+    [0.132, 0.856, 1.166, 1.448, 1.606, 1.733, 1.837, 2.035],
+    [0.148, 0.854, 1.147, 1.407, 1.549, 1.660, 1.749, 1.910],
+    [0.164, 0.852, 1.128, 1.366, 1.492, 1.588, 1.664, 1.800],
+    [0.195, 0.844, 1.086, 1.282, 1.379, 1.449, 1.501, 1.625],
+    [0.225, 0.832, 1.041, 1.198, 1.270, 1.318, 1.351, 1.465],
+    [0.254, 0.817, 0.994, 1.116, 1.166, 1.197, 1.216, 1.280],
+    [0.282, 0.799, 0.945, 1.035, 1.069, 1.087, 1.097, 1.130],
+    [0.307, 0.777, 0.895, 0.959, 0.980, 0.990, 1.995, 1.000],
+    [0.330, 0.752, 0.844, 0.888, 0.900, 0.905, 0.907, 0.910],
+    [0.360, 0.711, 0.771, 0.793, 0.798, 0.799, 0.800, 0.802],
+    [0.396, 0.636, 0.660, 0.666, 0.666, 0.667, 0.667, 0.668]]
+)
+
+_INDEX_SW = [
+    3, 2.5, 2.2, 2, 1.8, 1.6, 1.4, 1.2, 1,
+    0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0,
+    -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1,
+    -1.2, -1.4, -1.6, -1.8, -2., -2.2, -2.5, -3.
+]
+
+_COL_SW = [0.5, 0.2, 0.1, 0.04, 0.02, 0.01, 0.005, 0.001]
+
+t_pearson3_sw = pd.DataFrame(data=_DATA_SW, index=_INDEX_SW, columns=_COL_SW)
+
+# Tabel dari Soetopo hal. 105
+# Tabel Distribusi Pearson Type III (nilai K)
+
+# KODE: ST
+
+_DATA_ST = [
+    [-4.051, -2.003, -1.180, -0.420, 0.396, 0.636, 0.660, 0.666, 0.666, 0.667, 0.667, 0.667],
+    [-4.013, -2.007, -1.195, -0.440, 0.390, 0.651, 0.681, 0.688, 0.689, 0.690, 0.690, 0.690],
+    [-3.973, -2.010, -1.210, -0.460, 0.384, 0.666, 0.702, 0.712, 0.714, 0.714, 0.714, 0.714],
+    [-3.932, -2.012, -1.224, -0.479, 0.376, 0.681, 0.724, 0.738, 0.740, 0.740, 0.741, 0.741],
+    [-3.889, -2.013, -1.238, -0.499, 0.369, 0.696, 0.747, 0.765, 0.768, 0.769, 0.769, 0.769],
+    [-3.845, -2.012, -1.250, -0.518, 0.360, 0.711, 0.771, 0.793, 0.798, 0.799, 0.800, 0.800],
+    [-3.800, -2.011, -1.262, -0.537, 0.351, 0.725, 0.795, 0.823, 0.830, 0.832, 0.833, 0.833],
+    [-3.753, -2.009, -1.274, -0.555, 0.341, 0.739, 0.819, 0.855, 0.864, 0.867, 0.869, 0.869],
+    [-3.705, -2.006, -1.284, -0.574, 0.330, 0.752, 0.844, 0.888, 0.900, 0.905, 0.907, 0.909],
+    [-3.656, -2.001, -1.294, -0.592, 0.319, 0.765, 0.869, 0.923, 0.939, 0.946, 0.949, 0.952],
+    [-3.605, -1.996, -1.303, -0.609, 0.307, 0.777, 0.895, 0.959, 0.980, 0.990, 0.995, 0.999],
+    [-3.553, -1.989, -1.311, -0.627, 0.294, 0.788, 0.920, 0.997, 1.023, 1.037, 1.044, 1.051],
+    [-3.499, -1.981, -1.318, -0.643, 0.282, 0.799, 0.945, 1.035, 1.069, 1.087, 1.097, 1.107],
+    [-3.444, -1.972, -1.324, -0.660, 0.268, 0.808, 0.970, 1.075, 1.116, 1.140, 1.155, 1.170],
+    [-3.388, -1.962, -1.329, -0.675, 0.254, 0.817, 0.994, 1.116, 1.166, 1.197, 1.216, 1.238],
+    [-3.330, -1.951, -1.333, -0.691, 0.240, 0.825, 1.018, 1.157, 1.217, 1.256, 1.282, 1.313],
+    [-3.271, -1.938, -1.337, -0.705, 0.225, 0.832, 1.041, 1.198, 1.270, 1.318, 1.351, 1.394],
+    [-3.211, -1.925, -1.339, -0.719, 0.210, 0.838, 1.064, 1.240, 1.324, 1.383, 1.424, 1.482],
+    [-3.149, -1.910, -1.340, -0.733, 0.195, 0.844, 1.086, 1.282, 1.379, 1.449, 1.501, 1.577],
+    [-3.087, -1.894, -1.341, -0.745, 0.180, 0.848, 1.107, 1.324, 1.435, 1.518, 1.581, 1.678],
+    [-3.023, -1.877, -1.340, -0.758, 0.164, 0.852, 1.128, 1.366, 1.492, 1.588, 1.664, 1.786],
+    [-2.957, -1.859, -1.339, -0.769, 0.148, 0.854, 1.147, 1.407, 1.549, 1.660, 1.749, 1.899],
+    [-2.891, -1.839, -1.336, -0.780, 0.132, 0.856, 1.166, 1.448, 1.606, 1.733, 1.837, 2.017],
+    [-2.824, -1.819, -1.333, -0.790, 0.116, 0.857, 1.183, 1.489, 1.663, 1.806, 1.926, 2.141],
+    [-2.755, -1.797, -1.329, -0.800, 0.099, 0.857, 1.200, 1.528, 1.720, 1.880, 2.016, 2.268],
+    [-2.686, -1.774, -1.323, -0.808, 0.083, 0.857, 1.216, 1.567, 1.777, 1.955, 2.108, 2.399],
+    [-2.615, -1.750, -1.317, -0.816, 0.067, 0.855, 1.231, 1.606, 1.834, 2.029, 2.201, 2.533],
+    [-2.544, -1.726, -1.309, -0.824, 0.050, 0.853, 1.245, 1.643, 1.890, 2.104, 2.294, 2.669],
+    [-2.472, -1.700, -1.301, -0.830, 0.033, 0.850, 1.258, 1.680, 1.945, 2.178, 2.388, 2.808],
+    [-2.400, -1.673, -1.292, -0.836, 0.017, 0.846, 1.270, 1.716, 2.000, 2.253, 2.482, 2.948],
+    [-2.326, -1.645, -1.282, -0.842, 0.000, 0.842, 1.282, 1.751, 2.054, 2.326, 2.576, 3.090],
+    [-2.253, -1.616, -1.270, -0.846, -0.017, 0.836, 1.292, 1.785, 2.107, 2.400, 2.670, 3.233],
+    [-2.178, -1.586, -1.258, -0.850, -0.033, 0.830, 1.301, 1.818, 2.159, 2.472, 2.763, 3.377],
+    [-2.104, -1.555, -1.245, -0.853, -0.050, 0.824, 1.309, 1.849, 2.211, 2.544, 2.856, 3.521],
+    [-2.029, -1.524, -1.231, -0.855, -0.067, 0.816, 1.317, 1.880, 2.261, 2.615, 2.949, 3.666],
+    [-1.955, -1.491, -1.216, -0.857, -0.083, 0.808, 1.323, 1.910, 2.311, 2.686, 3.041, 3.811],
+    [-1.880, -1.458, -1.200, -0.857, -0.099, 0.800, 1.329, 1.939, 2.359, 2.755, 3.132, 3.956],
+    [-1.806, -1.423, -1.183, -0.857, -0.116, 0.790, 1.333, 1.967, 2.407, 2.824, 3.223, 4.100],
+    [-1.733, -1.389, -1.166, -0.856, -0.132, 0.780, 1.336, 1.993, 2.453, 2.891, 3.312, 4.244],
+    [-1.660, -1.353, -1.147, -0.854, -0.148, 0.769, 1.339, 2.018, 2.498, 2.957, 3.401, 4.388],
+    [-1.588, -1.317, -1.128, -0.852, -0.164, 0.758, 1.340, 2.043, 2.542, 3.023, 3.489, 4.531],
+    [-1.518, -1.280, -1.107, -0.848, -0.180, 0.745, 1.341, 2.066, 2.585, 3.087, 3.575, 4.673],
+    [-1.449, -1.243, -1.086, -0.844, -0.195, 0.733, 1.340, 2.088, 2.626, 3.149, 3.661, 4.815],
+    [-1.383, -1.206, -1.064, -0.838, -0.210, 0.719, 1.339, 2.108, 2.667, 3.211, 3.745, 4.955],
+    [-1.318, -1.168, -1.041, -0.832, -0.225, 0.705, 1.337, 2.128, 2.706, 3.271, 3.828, 5.095],
+    [-1.256, -1.131, -1.018, -0.825, -0.240, 0.691, 1.333, 2.146, 2.743, 3.330, 3.910, 5.234],
+    [-1.197, -1.093, -0.994, -0.817, -0.254, 0.675, 1.329, 2.163, 2.780, 3.388, 3.990, 5.371],
+    [-1.140, -1.056, -0.970, -0.808, -0.268, 0.660, 1.324, 2.179, 2.815, 3.444, 4.069, 5.507],
+    [-1.087, -1.020, -0.945, -0.799, -0.282, 0.643, 1.318, 2.193, 2.848, 3.499, 4.147, 5.642],
+    [-1.037, -0.984, -0.920, -0.788, -0.294, 0.627, 1.311, 2.207, 2.881, 3.553, 4.223, 5.775],
+    [-0.990, -0.949, -0.895, -0.777, -0.307, 0.609, 1.303, 2.219, 2.912, 3.605, 4.298, 5.908],
+    [-0.946, -0.915, -0.869, -0.765, -0.319, 0.592, 1.294, 2.230, 2.942, 3.656, 4.372, 6.039],
+    [-0.905, -0.882, -0.844, -0.752, -0.330, 0.574, 1.284, 2.240, 2.970, 3.705, 4.444, 6.168],
+    [-0.867, -0.850, -0.819, -0.739, -0.341, 0.555, 1.274, 2.248, 2.997, 3.753, 4.515, 6.296],
+    [-0.832, -0.819, -0.795, -0.725, -0.351, 0.537, 1.262, 2.256, 3.023, 3.800, 4.584, 6.423],
+    [-0.799, -0.790, -0.771, -0.711, -0.360, 0.518, 1.250, 2.262, 3.048, 3.845, 4.652, 6.548],
+    [-0.769, -0.762, -0.747, -0.696, -0.369, 0.499, 1.238, 2.267, 3.071, 3.889, 4.718, 6.672],
+    [-0.740, -0.736, -0.724, -0.681, -0.376, 0.479, 1.224, 2.272, 3.093, 3.932, 4.783, 6.794],
+    [-0.714, -0.711, -0.702, -0.666, -0.384, 0.460, 1.210, 2.275, 3.114, 3.973, 4.847, 6.915],
+    [-0.690, -0.688, -0.681, -0.651, -0.390, 0.440, 1.195, 2.277, 3.134, 4.013, 4.909, 7.034],
+    [-0.667, -0.665, -0.660, -0.636, -0.396, 0.420, 1.180, 2.278, 3.152, 4.051, 4.970, 7.152]
+]
+
+_INDEX_ST = np.arange(-30, 31, 1)/10
+
+_COL_ST = np.array([99, 95, 90, 80, 50, 20, 10, 4, 2, 1, 0.5, 0.1])/100
+
+t_pearson3_st = pd.DataFrame(data=_DATA_ST, index=_INDEX_ST, columns=_COL_ST)
+
+# Dari buku Limantara hal. 107-109
+# Tabel Distribsi log Pearson Tipe III Nilai G
+# Untuk Cs Positif & Negatif
+
+# KODE: LM
+
+_DATA_LM = [
+    [-0.667, -0.665, -0.660, -0.636, -0.396, 0.420, 1.180, 2.278, 3.152, 4.061, 4.970],
+    [-0.690, -0.688, -0.681, -0.651, -0.390, 0.440, 1.196, 2.277, 3.134, 4.013, 4.909],
+    [-0.714, -0.711, -0.702, -0.666, -0.384, 0.460, 1.210, 2.275, 3.114, 3.973, 4.847],
+    [-0.740, -0.736, -0.724, -0.681, -0.376, 0.479, 1.224, 2.272, 3.097, 3.932, 4.783],
+    [-0.769, -0.762, -0.747, -0.695, -0.368, 0.499, 1.238, 2.267, 3.071, 3.889, 4.718],
+    [-0.799, -0.790, -0.771, -0.711, -0.360, 0.518, 1.250, 2.262, 3.048, 3.845, 4.652],
+    [-0.832, -0.819, -0.795, -0.725, -0.351, 0.537, 1.262, 2.256, 3.029, 3.800, 4.584],
+    [-0.867, -0.850, -0.819, -0.739, -0.341, 0.555, 1.274, 2.248, 2.997, 3.753, 4.515],
+    [-0.905, -0.882, -0.844, -0.752, -0.330, 0.574, 1.284, 2.240, 2.970, 3.705, 4.454],
+    [-0.946, -0.914, -0.869, -0.765, -0.319, 0.592, 1.294, 2.230, 2.942, 3.656, 4.372],
+    [-0.990, -0.949, -0.896, -0.777, -0.307, 0.609, 1.302, 2.219, 2.912, 3.605, 4.298],
+    [-1.037, -0.984, -0.920, -0.788, -0.294, 0.627, 1.310, 2.207, 2.881, 3.553, 4.223],
+    [-1.087, -1.020, -0.945, -0.799, -0.282, 0.643, 1.318, 2.193, 2.848, 3.499, 4.147],
+    [-1.140, -1.056, -0.970, -0.808, -0.268, 0.660, 1.324, 2.179, 2.815, 3.444, 4.069],
+    [-1.197, -1.093, -0.994, -0.817, -0.254, 0.675, 1.329, 2.163, 2.780, 3.388, 3.990],
+    [-1.256, -1.131, -1.018, -0.825, -0.240, 0.690, 1.333, 2.146, 2.745, 3.330, 3.910],
+    [-1.318, -1.163, -1.041, -0.832, -0.225, 0.705, 1.337, 2.128, 2.706, 3.271, 3.828],
+    [-1.388, -1.206, -1.064, -0.838, -0.210, 0.719, 1.339, 2.108, 2.666, 3.211, 3.745],
+    [-1.449, -1.243, -1.086, -0.844, -0.195, 0.732, 1.340, 2.087, 2.626, 3.149, 3.661],
+    [-1.518, -1.280, -1.107, -0.848, -0.180, 0.745, 1.341, 2.066, 2.585, 3.087, 3.575],
+    [-1.588, -1.317, -1.128, -0.852, -0.164, 0.758, 1.340, 2.043, 2.542, 3.022, 3.489],
+    [-1.660, -1.353, -1.147, -0.854, -0.148, 0.769, 1.339, 2.018, 2.498, 2.967, 3.401],
+    [-1.733, -1.388, -1.166, -0.856, -0.132, 0.780, 1.336, 1.993, 2.453, 2.891, 3.312],
+    [-1.806, -1.423, -1.183, -0.857, -0.116, 0.790, 1.333, 1.967, 2.407, 2.824, 3.223],
+    [-1.880, -1.458, -1.200, -0.857, -0.099, 0.800, 1.328, 1.939, 2.359, 2.755, 3.123],
+    [-1.965, -1.491, -1.216, -0.856, -0.083, 0.808, 1.323, 1.910, 2.311, 2.686, 3.041],
+    [-2.029, -1.524, -1.231, -0.855, -0.066, 0.816, 1.317, 1.880, 2.261, 2.615, 2.949],
+    [-2.104, -1.555, -1.245, -0.853, -0.050, 0.824, 1.309, 1.849, 2.211, 2.544, 2.856],
+    [-2.175, -1.586, -1.258, -0.850, -0.033, 0.830, 1.301, 1.818, 2.159, 2.472, 2.763],
+    [-2.225, -1.616, -1.270, -0.846, -0.017, 0.836, 1.292, 1.785, 2.107, 2.400, 2.670],
+    [-2.326, -1.645, -1.282, -0.842, 0.000, 0.842, 1.282, 1.751, 2.064, 2.064, 2.576],
+    [-2.400, -1.673, -1.292, -0.836, 0.017, 0.846, 1.270, 1.716, 2.000, 2.252, 2.482],
+    [-2.472, -1.700, -1.301, -0.830, 0.033, 0.850, 1.258, 1.680, 1.945, 2.178, 2.388],
+    [-2.544, -1.762, -1.309, -0.824, 0.050, 0.853, 1.245, 0.163, 1.890, 2.104, 2.294],
+    [-2.615, -1.750, -1.317, -0.816, 0.066, 0.855, 1.231, 1.606, 1.834, 2.029, 2.201],
+    [-2.686, -1.774, -1.323, -0.808, 0.083, 0.856, 1.216, 1.567, 1.777, 1.955, 2.108],
+    [-2.755, -1.797, -1.328, -0.800, 0.099, 0.857, 1.200, 1.528, 1.720, 1.880, 2.016],
+    [-2.824, -1.819, -1.333, -0.790, 0.116, 0.857, 1.183, 1.488, 1.633, 1.800, 1.936],
+    [-2.891, -1.839, -1.336, -0.780, 0.132, 0.856, 1.166, 1.484, 1.608, 1.733, 1.837],
+    [-2.957, -1.858, -1.339, -0.769, 0.148, 0.854, 1.147, 1.407, 1.549, 1.660, 1.749],
+    [-3.022, -1.877, -1.340, -0.758, 0.164, 0.852, 1.108, 1.366, 1.492, 1.588, 1.664],
+    [-3.087, -1.894, -1.341, -0.745, 0.180, 0.848, 1.107, 1.324, 1.435, 1.518, 1.581],
+    [-3.149, -1.910, -1.340, -0.732, 0.195, 0.844, 1.086, 1.282, 1.379, 1.449, 1.501],
+    [-3.211, -1.925, -1.339, -0.719, 0.210, 0.838, 1.064, 1.240, 1.324, 1.383, 1.424],
+    [-3.271, -1.938, -1.337, -0.705, 0.225, 0.832, 1.041, 1.196, 1.270, 1.316, 1.351],
+    [-3.330, -1.961, -1.333, -0.690, 0.240, 0.825, 1.018, 1.157, 1.217, 1.256, 1.282],
+    [-3.388, -1.962, -1.329, -0.675, 0.254, 0.817, 0.994, 1.116, 1.168, 1.197, 1.216],
+    [-3.444, -1.972, -1.324, -0.660, 0.268, 0.808, 0.970, 1.075, 1.116, 1.140, 1.155],
+    [-3.499, -1.981, -1.318, -0.643, 0.282, 0.799, 0.945, 1.035, 1.069, 1.087, 1.097],
+    [-3.533, -1.989, -1.310, -0.627, 0.294, 0.788, 0.920, 0.996, 1.023, 1.037, 1.044],
+    [-3.605, -1.996, -1.302, -0.609, 0.307, 0.777, 0.895, 0.969, 0.980, 0.990, 0.995],
+    [-3.656, -2.001, -1.294, -0.592, 0.319, 0.765, 0.869, 0.923, 0.939, 0.346, 0.949],
+    [-3.705, -2.006, -1.284, -0.574, 0.330, 0.732, 0.849, 0.888, 0.900, 0.905, 0.907],
+    [-3.753, -2.009, -1.274, -0.555, 0.341, 0.739, 0.819, 0.855, 0.864, 0.867, 0.869],
+    [-3.800, -2.011, -1.262, -0.537, 0.351, 0.725, 0.795, 0.823, 0.830, 0.832, 0.833],
+    [-3.845, -2.012, -1.250, -0.518, 0.360, 0.711, 0.771, 0.793, 0.796, 0.799, 0.800],
+    [-3.889, -2.013, -1.238, -0.499, 0.368, 0.696, 0.747, 0.764, 0.767, 0.769, 0.769],
+    [-3.932, -2.011, -1.224, -0.479, 0.376, 0.681, 0.724, 0.738, 0.740, 0.740, 0.741],
+    [-3.973, -2.010, -1.210, -0.460, 0.384, 0.666, 0.702, 0.712, 0.714, 0.734, 0.714],
+    [-4.013, -2.007, -1.195, -0.440, 0.330, 0.651, 0.681, 0.683, 0.689, 0.690, 0.690],
+    [-4.051, -2.003, -1.180, -0.420, 0.390, 0.636, 0.660, 0.666, 0.666, 0.667, 0.667]
+]
+
+_INDEX_LM = np.arange(30, -31, -1) / 10
+
+_COL_LM =  np.array([99, 95, 90, 80, 50, 20, 10, 4, 2, 1, 0.5])/100
+
+t_pearson3_lm = pd.DataFrame(data=_DATA_LM, index=_INDEX_LM, columns=_COL_LM)
+
+# KODE FUNGSI INTERPOLASI DARI TABEL
+
+def _func_interp_bivariate(df):
+    "Membuat fungsi dari tabel untuk interpolasi bilinear"
+    table = df[df.columns.sort_values()].sort_index().copy()
+
+    x = table.index
+    y = table.columns
+    z = table.to_numpy()
+
+    # penggunaan kx=1, ky=1 untuk interpolasi linear antara 2 titik
+    # tidak menggunakan (cubic) spline interpolation
+    return interpolate.RectBivariateSpline(x, y, z, kx=1, ky=1)
+
+def _as_value(x):
+    x = np.around(x, 4)
+    return x.flatten() if x.size > 1 else x.item()
+
+
+def find_K(prob, skew_log, source='scipy'):
+    "Mencari nilai K berdasarkan probabilitas dan skewness logaritmik"
+    prob = np.array(prob)
+    if source.lower() == 'scipy':
+        #ref: https://github.com/hidrokit/hidrokit/discussions/156
+        if skew_log >= 0:
+            return np.around(stats.pearson3.ppf(1-prob, skew_log), 4)
+        else:
+            return np.around(stats.pearson3.ppf(prob, skew_log), 4)
+    elif source.lower() == 'soetopo':
+        func_pearson3_st = _func_interp_bivariate(t_pearson3_st)
+        return _as_value(func_pearson3_st(skew_log, prob, grid=False))
+    elif source.lower() == 'soewarno':
+        func_pearson3_sw = _func_interp_bivariate(t_pearson3_sw)
+        return _as_value(func_pearson3_sw(skew_log, prob, grid=False))
+    elif source.lower() == 'limantara':
+        func_pearson3_lm = _func_interp_bivariate(t_pearson3_lm)
+        return _as_value(func_pearson3_lm(skew_log, prob, grid=False))
+
+def calc_x_lp3(x, return_period=[5], source='scipy', show_stat=False):
+    "Menghitung besar X dengan kala ulang tertentu"
+    y = np.log10(x)
+    y_mean = np.mean(y)
+    y_std = np.std(y, ddof=1)
+    y_skew = stats.skew(y, bias=False)
+
+    prob = 1 / np.array(return_period)
+    k = find_K(prob, y_skew, source=source)
+
+    if show_stat:
+        print(f'y_mean = {y_mean:.5f}')
+        print(f'y_std = {y_std:.5f}')
+        print(f'y_skew = {y_skew:.5f}')
+        print(f'k = {k}')
+    
+    val_y = y_mean + k * y_std
+    val_x = np.power(10, val_y)
+    return val_x
+
+def freq_logpearson3(
+    df, col=None, 
+    return_period=[2, 5, 10, 20, 25, 50, 100], source='scipy', show_stat=False,
+    col_name='Log Pearson III'
+    ):
+
+    col = df.columns[0] if col is None else col
+
+    x = df[col].copy()
+
+    arr = calc_x_lp3(
+        x, return_period=return_period, show_stat=show_stat,
+        source=source
+    )
+
+    return pd.DataFrame(
+        data=arr, index=return_period, columns=[col_name]
+    )
+
+dict_table_source = {
+    'soewarno': t_pearson3_sw,
+    'soetopo': t_pearson3_st,
+    'limantara': t_pearson3_lm
+}
+
+def _find_prob_in_table(k, skew_log, table):
+    func_table = _func_interp_bivariate(table)
+    y = table.columns
+    x = func_table(skew_log, y, grid=False)
+    func_prob = interpolate.interp1d(x, y, kind='linear')
+    return _as_value(func_prob(k))
+
+def _calc_prob_in_table(k, skew_log, source='soewarno'):
+    if source.lower() in dict_table_source.keys():
+        return 1 - _find_prob_in_table(
+            k, skew_log, dict_table_source[source.lower()]
+        )
+    
+def calc_prob(k, skew_log, source='scipy'):
+    if source.lower() == 'scipy':
+        if skew_log >= 0:
+            return 1 - (1 - stats.pearson3.cdf(k, skew_log))
+        else:
+            return 1 - stats.pearson3.cdf(k, skew_log)
+    if source.lower() in dict_table_source.keys():
+        return _calc_prob_in_table(k, skew_log, source.lower())
diff --git a/hidrokit/contrib/taruma/hk127.py b/hidrokit/contrib/taruma/hk127.py
new file mode 100644
index 0000000..a110842
--- /dev/null
+++ b/hidrokit/contrib/taruma/hk127.py
@@ -0,0 +1,437 @@
+"""manual:
+https://gist.github.com/taruma/ffa77e6f50a19fa5d05ab10e27d3266a"""
+
+import numpy as np
+import pandas as pd
+from scipy import stats
+
+# tabel dari gumbel
+# Statistics of Extremes oleh Gumbel p.228
+
+# KODE: GB
+
+_DATA_GB = [
+    [0.48430, 0.90430],
+    [0.49020, 0.92880],
+    [0.49520, 0.94970],
+    [0.49960, 0.96760],
+    [0.50350, 0.98330],
+    [0.50700, 0.99720],
+    [0.51000, 1.00950],
+    [0.51280, 1.02057],
+    [0.51570, 1.03160],
+    [0.51810, 1.04110],
+    [0.52020, 1.04930],
+    [0.52200, 1.05660],
+    [0.52355, 1.06283],
+    [0.52520, 1.06960],
+    [0.52680, 1.07540],
+    [0.52830, 1.08110],
+    [0.52960, 1.08640],
+    [0.53086, 1.09145],
+    [0.53200, 1.09610],
+    [0.53320, 1.10040],
+    [0.53430, 1.10470],
+    [0.53530, 1.10860],
+    [0.53622, 1.11238],
+    [0.53710, 1.11590],
+    [0.53800, 1.11930],
+    [0.53880, 1.12260],
+    [0.53960, 1.12550],
+    [0.54034, 1.12847],
+    [0.54100, 1.13130],
+    [0.54180, 1.13390],
+    [0.54240, 1.13630],
+    [0.54300, 1.13880],
+    [0.54362, 1.14132],
+    [0.54420, 1.14360],
+    [0.54480, 1.14580],
+    [0.54530, 1.14800],
+    [0.54580, 1.14990],
+    [0.54630, 1.15185],
+    [0.54680, 1.15380],
+    [0.54730, 1.15570],
+    [0.54770, 1.15740],
+    [0.54810, 1.15900],
+    [0.54854, 1.16066],
+    [0.54890, 1.16230],
+    [0.54930, 1.16380],
+    [0.54970, 1.16530],
+    [0.55010, 1.16670],
+    [0.55040, 1.16810],
+    [0.55080, 1.16960],
+    [0.55110, 1.17080],
+    [0.55150, 1.17210],
+    [0.55180, 1.17340],
+    [0.55208, 1.17467],
+    [0.55270, 1.17700],
+    [0.55330, 1.17930],
+    [0.55380, 1.18140],
+    [0.55430, 1.18340],
+    [0.55477, 1.18536],
+    [0.55520, 1.18730],
+    [0.55570, 1.18900],
+    [0.55610, 1.19060],
+    [0.55650, 1.19230],
+    [0.55688, 1.19382],
+    [0.55720, 1.19530],
+    [0.55760, 1.19670],
+    [0.55800, 1.19800],
+    [0.55830, 1.19940],
+    [0.55860, 1.20073],
+    [0.55890, 1.20200],
+    [0.55920, 1.20320],
+    [0.55950, 1.20440],
+    [0.55980, 1.20550],
+    [0.56002, 1.20649],
+    [0.56461, 1.22534],
+    [0.56715, 1.23598],
+    [0.56878, 1.24292],
+    [0.56993, 1.24786],
+    [0.57144, 1.25450],
+    [0.57240, 1.25880],
+    [0.57377, 1.26506],
+    [0.57450, 1.26851]
+]
+
+_INDEX_GB = (
+    list(range(8, 61)) +
+    list(range(62, 101, 2)) +
+    list(range(150, 301, 50)) +
+    [400, 500, 750, 1000]
+)
+
+_COL_GB = ['yn', 'sn']
+
+t_gumbel_gb = pd.DataFrame(
+    data=_DATA_GB, index=_INDEX_GB, columns=_COL_GB
+)
+
+# tabel dari soewarno
+# Tabel 3.11A & 3.11B p.129-130
+
+# KODE: GB
+
+_DATA_SW = [
+    [0.4592, 0.9496],
+    [0.4996, 0.9676],
+    [0.5053, 0.9933],
+    [0.5070, 0.9971],
+    [0.5100, 1.0095],
+    [0.5128, 1.0206],
+    [0.5157, 1.0316],
+    [0.5181, 1.0411],
+    [0.5202, 1.0493],
+    [0.5220, 1.0565],
+    [0.5236, 1.0628],
+    [0.5252, 1.0696],
+    [0.5268, 1.0754],
+    [0.5283, 1.0811],
+    [0.5296, 1.0864],
+    [0.5309, 1.0915],
+    [0.5320, 1.1961],
+    [0.5332, 1.1004],
+    [0.5343, 1.1047],
+    [0.5353, 1.1086],
+    [0.5362, 1.1124],
+    [0.5371, 1.1159],
+    [0.5380, 1.1193],
+    [0.5388, 1.1226],
+    [0.5396, 1.1255],
+    [0.5402, 1.1285],
+    [0.5410, 1.1313],
+    [0.5418, 1.1339],
+    [0.5424, 1.1363],
+    [0.5430, 1.1388],
+    [0.5436, 1.1413],
+    [0.5442, 1.1436],
+    [0.5448, 1.1458],
+    [0.5453, 1.1480],
+    [0.5458, 1.1499],
+    [0.5463, 1.1519],
+    [0.5468, 1.1538],
+    [0.5473, 1.1557],
+    [0.5477, 1.1574],
+    [0.5481, 1.1590],
+    [0.5485, 1.1607],
+    [0.5489, 1.1623],
+    [0.5493, 1.1638],
+    [0.5497, 1.1658],
+    [0.5501, 1.1667],
+    [0.5504, 1.1681],
+    [0.5508, 1.1696],
+    [0.5511, 1.1708],
+    [0.5518, 1.1721],
+    [0.5518, 1.1734],
+    [0.5521, 1.1747],
+    [0.5524, 1.1759],
+    [0.5527, 1.1770],
+    [0.5530, 1.1782],
+    [0.5533, 1.1793],
+    [0.5535, 1.1803],
+    [0.5538, 1.1814],
+    [0.5540, 1.1824],
+    [0.5543, 1.1834],
+    [0.5545, 1.1844],
+    [0.5548, 1.1854],
+    [0.5550, 1.1863],
+    [0.5552, 1.1873],
+    [0.5555, 1.1881],
+    [0.5557, 1.1890],
+    [0.5559, 1.1898],
+    [0.5561, 1.1906],
+    [0.5563, 1.1915],
+    [0.5565, 1.1923],
+    [0.5567, 1.1930],
+    [0.5569, 1.1938],
+    [0.5570, 1.1945],
+    [0.5572, 1.1953],
+    [0.5574, 1.1959],
+    [0.5576, 1.1967],
+    [0.5578, 1.1973],
+    [0.5580, 1.1980],
+    [0.5581, 1.1987],
+    [0.5583, 1.1994],
+    [0.5585, 1.2001],
+    [0.5586, 1.2007],
+    [0.5587, 1.2013],
+    [0.5589, 1.2020],
+    [0.5591, 1.2026],
+    [0.5592, 1.2032],
+    [0.5593, 1.2038],
+    [0.5595, 1.2044],
+    [0.5596, 1.2049],
+    [0.5598, 1.2055],
+    [0.5599, 1.2060],
+    [0.5600, 1.2065],
+]
+
+_INDEX_SW = list(range(10, 101))
+
+_COL_SW = ['yn', 'sn']
+
+t_gumbel_sw = pd.DataFrame(
+    data=_DATA_SW, index=_INDEX_SW, columns=_COL_SW
+)
+
+# Tabel dari Soetopo hal. 98
+# Tabel 12.1 Yn dan Sn Gumbel 
+
+# KODE: ST
+
+_DATA_ST = [
+    [0.4843, 0.9043],
+    [0.4902, 0.9288],
+    [0.4952, 0.9497],
+    [0.4996, 0.9676],
+    [0.5035, 0.9833],
+    [0.5070, 0.9972],
+    [0.5100, 1.0095],
+    [0.5128, 1.0205],
+    [0.5157, 1.0316],
+    [0.5181, 1.0411],
+    [0.5202, 1.0493],
+    [0.5220, 1.0566],
+    [0.5235, 1.0628],
+    [0.5252, 1.0696],
+    [0.5268, 1.0754],
+    [0.5283, 1.0811],
+    [0.5296, 1.0864],
+    [0.5309, 1.0915],
+    [0.5320, 1.0961],
+    [0.5332, 1.1004],
+    [0.5343, 1.1047],
+    [0.5353, 1.1086],
+    [0.5362, 1.1124],
+    [0.5371, 1.1159],
+    [0.5380, 1.1193],
+    [0.5388, 1.1226],
+    [0.5396, 1.1255],
+    [0.5402, 1.1285],
+    [0.5410, 1.1313],
+    [0.5418, 1.1339],
+    [0.5424, 1.1363],
+    [0.5430, 1.1388],
+    [0.5436, 1.1413],
+    [0.5442, 1.1436],
+    [0.5448, 1.1458],
+    [0.5453, 1.1480],
+    [0.5458, 1.1499],
+    [0.5463, 1.1519],
+    [0.5468, 1.1538],
+    [0.5473, 1.1557],
+    [0.5477, 1.1574],
+    [0.5481, 1.1590],
+    [0.5485, 1.1607],
+    [0.5489, 1.1623],
+    [0.5493, 1.1638],
+    [0.5497, 1.1658],
+    [0.5501, 1.1667],
+    [0.5504, 1.1681],
+    [0.5508, 1.1696],
+    [0.5511, 1.1708],
+    [0.5515, 1.1721],
+    [0.5518, 1.1734],
+    [0.5521, 1.1747],
+    [0.5524, 1.1759],
+    [0.5527, 1.1770],
+    [0.5530, 1.1782],
+    [0.5533, 1.1793],
+    [0.5535, 1.1803],
+    [0.5538, 1.1814],
+    [0.5540, 1.1824],
+    [0.5543, 1.1834],
+    [0.5545, 1.1844],
+    [0.5548, 1.1854],
+    [0.5550, 1.1863],
+    [0.5552, 1.1873],
+    [0.5555, 1.1881],
+    [0.5557, 1.1890],
+    [0.5559, 1.1898],
+    [0.5561, 1.1906],
+    [0.5563, 1.1915],
+    [0.5565, 1.1923],
+    [0.5567, 1.1930],
+    [0.5569, 1.1938],
+    [0.5570, 1.1945],
+    [0.5572, 1.1953],
+    [0.5574, 1.1959],
+    [0.5576, 1.1967],
+    [0.5578, 1.1973],
+    [0.5580, 1.1980],
+    [0.5581, 1.1987],
+    [0.5583, 1.1994],
+    [0.5585, 1.2001],
+    [0.5586, 1.2007],
+    [0.5587, 1.2013],
+    [0.5589, 1.2020],
+    [0.5591, 1.2026],
+    [0.5592, 1.2032],
+    [0.5593, 1.2038],
+    [0.5595, 1.2044],
+    [0.5596, 1.2049],
+    [0.5598, 1.2055],
+    [0.5599, 1.2060],
+    [0.5600, 1.2065],
+]
+
+_INDEX_ST = list(range(8, 101))
+
+_COL_ST = ['yn', 'sn']
+
+t_gumbel_st = pd.DataFrame(
+    data=_DATA_ST, index=_INDEX_ST, columns=_COL_ST
+)
+
+def _find_in_table(val, table, y_col=None, x_col=None):
+    x = table.index if x_col is None else table[x_col]
+    y = table.iloc[:, 0] if y_col is None else table[y_col]
+    return np.interp(val, x, y)
+
+def _find_Yn_Sn(n, table):
+    yn = _find_in_table(n, table, y_col='yn')
+    sn = _find_in_table(n, table, y_col='sn')
+    return yn, sn
+
+def find_coef(n, source='gumbel'):
+    if source.lower() == 'gumbel':
+        return _find_Yn_Sn(n, t_gumbel_gb)
+    if source.lower() == 'soewarno':
+        return _find_Yn_Sn(n, t_gumbel_sw)
+    if source.lower() == 'soetopo':
+        return _find_Yn_Sn(n, t_gumbel_st)
+
+def calc_K(n, return_period, source='gumbel', show_stat=False):
+    return_period = np.array(return_period)
+
+    if source.lower() == 'scipy':
+        # todo: perhitungan probabilitasnya belum dapat dipastikan formulanya
+        prob = 1 - 1/return_period
+        # prob = 1 - np.log(return_period/(return_period-1))
+        return stats.gumbel_r.ppf(prob)
+    elif source.lower() == 'powell':
+        return -np.sqrt(6)/np.pi *(np.euler_gamma+np.log(np.log(return_period/(return_period-1))))
+    else:
+        # dibuku Soewarno dinyatakan T>=20 menggunakan
+        # ln(T), tapi dicontohnya tidak mengikuti formula tersebut
+        # jadi yang digunakan rumus umumnya saja.
+        # if source.lower() == 'soewarno':
+        #     yt = []
+        #     for t in return_period:
+        #         if t <= 20:
+        #             yt += [-np.log(-np.log((t - 1)/t))]
+        #         else:
+        #             yt += [np.log(t)]
+        #     yt = np.array(yt)
+        # else:
+        #     yt = -np.log(-np.log((return_period - 1)/return_period))
+
+        yn, sn = find_coef(n, source=source)
+        yt = -np.log(-np.log((return_period - 1)/return_period))
+        K = (yt - yn) / sn
+
+        if show_stat:
+            print(f'y_n = {yn}')
+            print(f's_n = {sn}')
+            print(f'y_t = {yt}')
+        return K
+
+def calc_x_gumbel(x, return_period=[5], source='gumbel', show_stat=False):
+
+    x_mean = np.mean(x)
+    x_std = np.std(x, ddof=1)
+    n = len(x)
+
+    k = calc_K(n, return_period, source=source, show_stat=show_stat)
+
+    if show_stat:
+        print(f'x_mean = {x_mean:.5f}')
+        print(f'x_std = {x_std:.5f}')
+        print(f'k = {k}')
+    
+    val_x = x_mean + k * x_std
+    return val_x
+
+def freq_gumbel(
+    df, col=None,
+    return_period=[2, 5, 10, 20, 25, 50, 100], source='gumbel', show_stat=False,
+    col_name='Gumbel'):
+
+    col = df.columns[0] if col is None else col
+
+    x = df[col].copy()
+
+    arr = calc_x_gumbel(
+        x, return_period=return_period, show_stat=show_stat,
+        source=source
+    )
+
+    return pd.DataFrame(
+        data=arr, index=return_period, columns=[col_name]
+    )
+
+def _calc_T(P):
+    return 1 / (1-np.exp(-np.exp(-P)))
+
+def _calc_prob_from_table(k, n, source='gumbel'):
+    yn, sn = find_coef(n, source=source)
+    P = k * sn + yn
+    T = _calc_T(P)
+    return np.around(1-1/T, 3)
+
+def calc_prob(k, n, source='gumbel'):
+    if source.lower() == 'gumbel':
+        return _calc_prob_from_table(k, n, source=source)
+    if source.lower() == 'soewarno':
+        return _calc_prob_from_table(k, n, source=source)
+    if source.lower() == 'soetopo':
+        return _calc_prob_from_table(k, n, source=source)
+    if source.lower() == 'scipy':
+        return stats.gumbel_r.cdf(k)
+    if source.lower() == 'powell':
+        # persamaan ini ditemukan menggunakan wolfram alpha
+        # x = e^(e^(-(π K)/sqrt(6) - p))/(e^(e^(-(π K)/sqrt(6) - p)) - 1)
+        _top = np.exp(np.exp(-(np.pi*k)/np.sqrt(6)-np.euler_gamma))
+        _bot = _top - 1
+        T = _top / _bot
+        return 1-1/T
diff --git a/hidrokit/contrib/taruma/hk140.py b/hidrokit/contrib/taruma/hk140.py
new file mode 100644
index 0000000..f3ef113
--- /dev/null
+++ b/hidrokit/contrib/taruma/hk140.py
@@ -0,0 +1,168 @@
+"""manual:
+https://gist.github.com/taruma/5d16baf90016d8a08c6870b674226691"""
+
+import numpy as np
+import pandas as pd
+from scipy import stats
+from hidrokit.contrib.taruma import hk172, hk124, hk127, hk126 
+
+frek_normal, frek_lognormal, frek_gumbel, frek_logpearson3 = hk172, hk124, hk127, hk126
+
+# tabel dari soetopo hal. 139
+# Tabel Nilai Kritis (Dcr) Untuk Uji Kolmogorov-Smirnov
+
+# KODE: ST
+
+_DATA_ST = [
+    [0.900, 0.925, 0.950, 0.975, 0.995],
+    [0.684, 0.726, 0.776, 0.842, 0.929],
+    [0.565, 0.597, 0.642, 0.708, 0.829],
+    [0.494, 0.525, 0.564, 0.624, 0.734],
+    [0.446, 0.474, 0.510, 0.563, 0.669],
+    [0.410, 0.436, 0.470, 0.521, 0.618],
+    [0.381, 0.405, 0.438, 0.486, 0.577],
+    [0.358, 0.381, 0.411, 0.457, 0.543],
+    [0.339, 0.360, 0.388, 0.432, 0.514],
+    [0.322, 0.342, 0.368, 0.409, 0.486],
+    [0.307, 0.326, 0.352, 0.391, 0.468],
+    [0.295, 0.313, 0.338, 0.375, 0.450],
+    [0.284, 0.302, 0.325, 0.361, 0.433],
+    [0.274, 0.292, 0.314, 0.349, 0.418],
+    [0.266, 0.283, 0.304, 0.338, 0.404],
+    [0.258, 0.274, 0.295, 0.328, 0.391],
+    [0.250, 0.266, 0.286, 0.318, 0.380],
+    [0.244, 0.259, 0.278, 0.309, 0.370],
+    [0.237, 0.252, 0.272, 0.301, 0.361],
+    [0.231, 0.246, 0.264, 0.294, 0.352],
+]
+
+_INDEX_ST = range(1, 21)
+
+_COL_ST = [0.2, 0.15, 0.1, 0.05, 0.01]
+
+t_dcr_st = pd.DataFrame(
+    data=_DATA_ST, index=_INDEX_ST, columns=_COL_ST
+)
+
+# tabel dari soewarno hal. 139
+# Tabel Nilai Kritis (Dcr) Untuk Uji Kolmogorov-Smirnov
+
+# KODE: SW
+
+_DATA_SW = [
+    [0.45, 0.51, 0.56, 0.67],
+    [0.32, 0.37, 0.41, 0.49],
+    [0.27, 0.3 , 0.34, 0.4 ],
+    [0.23, 0.26, 0.29, 0.35],
+    [0.21, 0.24, 0.26, 0.32],
+    [0.19, 0.22, 0.24, 0.29],
+    [0.18, 0.2 , 0.22, 0.27],
+    [0.17, 0.19, 0.21, 0.25],
+    [0.16, 0.18, 0.2 , 0.24],
+    [0.15, 0.17, 0.19, 0.23]
+]
+
+_INDEX_SW = range(5, 51, 5)
+
+_COL_SW = [0.2, 0.1, 0.05, 0.01]
+
+t_dcr_sw = pd.DataFrame(
+    data=_DATA_SW, index=_INDEX_SW, columns=_COL_SW
+)
+
+# KODE FUNGSI INTERPOLASI DARI TABEL
+
+from scipy import interpolate
+
+def _func_interp_bivariate(df):
+    "Membuat fungsi dari tabel untuk interpolasi bilinear"
+    table = df[df.columns.sort_values()].sort_index().copy()
+
+    x = table.index
+    y = table.columns
+    z = table.to_numpy()
+
+    # penggunaan kx=1, ky=1 untuk interpolasi linear antara 2 titik
+    # tidak menggunakan (cubic) spline interpolation
+    return interpolate.RectBivariateSpline(x, y, z, kx=1, ky=1)
+
+def _as_value(x, dec=4):
+    x = np.around(x, dec)
+    return x.flatten() if x.size > 1 else x.item()
+
+def _calc_k(x):
+    return (x - x.mean()) / x.std()
+
+table_source = {
+    'soewarno': t_dcr_sw,
+    'soetopo': t_dcr_st
+}
+
+anfrek = {
+    'normal': frek_normal,
+    'lognormal': frek_lognormal,
+    'gumbel': frek_gumbel,
+    'logpearson3': frek_logpearson3
+}
+
+def calc_dcr(alpha, n, source='scipy'):
+    alpha = np.array(alpha)
+    if source.lower() == 'scipy':
+        # ref: https://stackoverflow.com/questions/53509986/
+        return stats.ksone.ppf(1-alpha/2, n)
+    elif source.lower() in table_source.keys():
+        func_table = _func_interp_bivariate(table_source[source.lower()])
+        # untuk soewarno 2 angka dibelakang koma, dan soetopo = 3
+        dec = (source.lower() == 'soetopo') + 2
+        return _as_value(func_table(n, alpha, grid=False), dec)
+
+def kstest(
+    df, col=None, dist='normal', source_dist='scipy', 
+    alpha=0.05, source_dcr='scipy', show_stat=True, report='result'
+    ):
+
+    source_dist = 'gumbel' if dist.lower() == 'gumbel' else source_dist
+
+    col = df.columns[0] if col is None else col
+    data = df[[col]].copy()
+    n = len(data)
+    data = data.rename({col: 'x'}, axis=1)
+    data = data.sort_values('x')
+    data['no'] = np.arange(n) + 1
+
+    # w = weibull
+    data['p_w'] = data.no / (n+1)
+    
+    if dist.lower() in ['normal', 'gumbel']:
+        data['k'] = _calc_k(data.x)
+    if dist.lower() in ['lognormal', 'logpearson3']:
+        data['log_x'] = np.log10(data.x)
+        data['k'] = _calc_k(data.log_x)
+
+    func = anfrek[dist.lower()]
+
+    if dist.lower() in ['normal', 'lognormal']:
+        parameter = ()
+    elif dist.lower() == 'gumbel':
+        parameter = (n,)
+    elif dist.lower() == 'logpearson3':
+        parameter = (data.log_x.skew(),)
+    
+    # d = distribusi
+    data['p_d'] = func.calc_prob(data.k, source=source_dist, *parameter) 
+    data['d'] = (data.p_w - data.p_d).abs()
+    dmax = data.d.max()
+    dcr = calc_dcr(alpha, n, source=source_dcr)
+    result = int(dmax < dcr)
+    result_text = ['Distribusi Tidak Diterima', 'Distribusi Diterima']
+
+    if show_stat:
+        print(f'Periksa Kecocokan Distribusi {dist.title()}')
+        print(f'Delta Kritikal = {dcr:.5f}')
+        print(f'Delta Max = {dmax:.5f}')
+        print(f'Result (Dmax < Dcr) = {result_text[result]}')
+
+    if report.lower() == 'result':
+        return data['no x p_w p_d d'.split()]
+    elif report.lower() == 'full':
+        return data
diff --git a/hidrokit/contrib/taruma/hk141.py b/hidrokit/contrib/taruma/hk141.py
new file mode 100644
index 0000000..5a4dd64
--- /dev/null
+++ b/hidrokit/contrib/taruma/hk141.py
@@ -0,0 +1,179 @@
+"""manual:
+https://gist.github.com/taruma/e250ab2685ba5b4c8facbf498cfb5cd8"""
+
+import numpy as np
+import pandas as pd
+from scipy import stats
+from hidrokit.contrib.taruma import hk172, hk124, hk127, hk126 
+
+frek_normal, frek_lognormal, frek_gumbel, frek_logpearson3 = hk172, hk124, hk127, hk126
+
+# tabel dari limantara hal. 117
+# Tabel Nilai Kritis untuk Distribusi Chi Square (X^2)
+
+# KODE: LM
+
+_DATA_LM = [
+    [0.039, 0.016, 0.698, 0.393, 3.841, 5.024, 6.635, 7.879],
+    [0.100, 0.201, 0.506, 0.103, 5.991, 0.738, 9.210, 10.597],
+    [0.717, 0.115, 0.216, 0.352, 7.815, 9.348, 11.345, 12.838],
+    [0.207, 0.297, 0.484, 0.711, 9.488, 11.143, 13.277, 14.860],
+    [0.412, 0.554, 0.831, 1.145, 11.070, 12.832, 15.086, 16.750],
+    [0.676, 0.872, 1.237, 1.635, 12.592, 14.449, 16.812, 18.548],
+    [0.989, 1.239, 1.690, 2.167, 14.067, 16.013, 18.475, 20.278],
+    [1.344, 1.646, 2.180, 2.733, 15.507, 17.535, 20.090, 21.955],
+    [1.735, 2.088, 2.700, 3.325, 16.919, 19.023, 21.666, 23.589],
+    [2.156, 2.558, 3.247, 3.940, 18.307, 20.483, 23.209, 25.188],
+    [2.603, 3.053, 3.816, 4.575, 19.675, 21.920, 24.725, 26.757],
+    [3.074, 3.571, 4.404, 5.226, 21.026, 23.337, 26.217, 28.300],
+    [3.565, 4.107, 5.009, 5.892, 22.362, 24.736, 27.688, 29.819],
+    [4.075, 4.660, 5.629, 6.571, 23.685, 26.119, 29.141, 31.319],
+    [4.601, 5.229, 6.262, 7.261, 24.996, 27.488, 30.578, 32.801],
+    [5.142, 5.812, 6.908, 7.962, 26.296, 28.845, 32.000, 34.267],
+    [5.697, 6.408, 7.564, 8.672, 27.587, 30.191, 33.409, 35.718],
+    [6.265, 7.015, 8.231, 9.390, 28.869, 31.526, 34.805, 37.156],
+    [6.884, 7.633, 8.907, 10.117, 30.144, 32.852, 36.191, 38.582],
+    [7.434, 8.260, 9.591, 10.851, 31.410, 34.170, 37.566, 39.997],
+    [8.034, 8.897, 10.283, 11.591, 32.671, 35.479, 38.932, 41.401],
+    [8.643, 9.542, 10.982, 12.338, 33.924, 36.781, 40.289, 42.796],
+    [9.260, 10.196, 11.689, 13.091, 36.172, 38.076, 41.638, 44.181],
+    [9.886, 10.856, 12.401, 13.848, 36.415, 39.364, 42.980, 45.558],
+    [10.520, 11.524, 13.120, 14.611, 37.652, 40.646, 44.314, 46.928],
+    [11.160, 12.198, 13.844, 15.379, 38.885, 41.923, 45.642, 48.290],
+    [11.808, 12.879, 14.573, 16.151, 40.113, 43.194, 46.963, 49.645],
+    [12.461, 13.565, 15.308, 16.928, 41.337, 44.461, 48.278, 50.993],
+    [13.121, 14.256, 16.047, 17.708, 42.557, 45.722, 49.588, 52.336],
+    [13.787, 14.953, 16.791, 18.493, 43.773, 46.979, 50.892, 53.672],
+]
+
+_INDEX_LM = range(1, 31)
+
+_COL_LM = [0.995, .99, .975, .95, .05, .025, 0.01, 0.005]
+
+t_chi_lm = pd.DataFrame(
+    data=_DATA_LM, index=_INDEX_LM, columns=_COL_LM
+)
+
+from scipy import interpolate
+
+def _func_interp_bivariate(df):
+    "Membuat fungsi dari tabel untuk interpolasi bilinear"
+    table = df[df.columns.sort_values()].sort_index().copy()
+
+    x = table.index
+    y = table.columns
+    z = table.to_numpy()
+
+    # penggunaan kx=1, ky=1 untuk interpolasi linear antara 2 titik
+    # tidak menggunakan (cubic) spline interpolation
+    return interpolate.RectBivariateSpline(x, y, z, kx=1, ky=1)
+
+def _as_value(x, dec=4):
+    x = np.around(x, dec)
+    return x.flatten() if x.size > 1 else x.item()
+
+table_source = {
+    'limantara': t_chi_lm
+}
+
+anfrek = {
+    'normal': frek_normal.calc_x_normal,
+    'lognormal': frek_lognormal.calc_x_lognormal,
+    'gumbel': frek_gumbel.calc_x_gumbel,
+    'logpearson3': frek_logpearson3.calc_x_lp3,
+}
+
+def _calc_k(n):
+    return np.floor(1 + 3.22 * np.log10(n)).astype(int)
+
+def _calc_dk(k, m):
+    return k - 1 - m
+
+def calc_xcr(alpha, dk, source='scipy'):
+    alpha = np.array(alpha)
+
+    if source.lower() in table_source.keys():
+        func_table = _func_interp_bivariate(table_source[source.lower()])
+        return _as_value(func_table(dk, alpha, grid=False), 3)
+    if source.lower() == 'scipy':
+        #ref: https://stackoverflow.com/questions/32301698
+        return stats.chi2.isf(alpha, dk)
+
+def chisquare(
+    df, col=None, dist='normal', source_dist='scipy',
+    alpha=0.05, source_xcr='scipy', show_stat=True,
+    ):
+
+    source_dist = 'gumbel' if dist.lower() == 'gumbel' else source_dist
+
+    col = df.columns[0] if col is None else col
+    data = df[[col]].copy()
+    n = len(data)
+    data = data.rename({col: 'x'}, axis=1)
+
+    if dist.lower() in ['lognormal', 'logpearson3']:
+        data['log_x'] = np.log10(data.x)
+
+    k = _calc_k(n)
+    prob_class = 1 / k
+    prob_list = np.linspace(0, 1, k+1)[::-1]
+    prob_seq = prob_list[1:-1]
+
+    func = anfrek[dist.lower()]
+
+    T = 1 / prob_seq
+    val_x = func(data.x, return_period=T, source=source_dist)
+
+    # Chi Square Table
+    calc_df = pd.DataFrame()
+    min = data.x.min()
+    max = data.x.max()
+    seq_x = np.concatenate([[min], val_x, [max]])
+
+    calc_df['no'] = range(1, k+1)
+
+    class_text = []
+    for i in range(seq_x.size-1):
+        if i == 0:
+            class_text += [f'X <= {seq_x[i+1]:.4f}']
+        elif i == seq_x.size-2:
+            class_text += [f'X > {seq_x[i]:.4f}']
+        else:
+            class_text += [f'{seq_x[i]:.4f} < X <= {seq_x[i+1]:.4f}']
+    calc_df['batas_kelas'] = class_text
+
+    # calculate fe
+    fe = []
+    for i in range(seq_x.size-1):
+        if i == 0:
+            fe += [(data.x <= seq_x[i+1]).sum()]
+        elif i == seq_x.size-2:
+            fe += [(data.x > seq_x[i]).sum()]
+        else:
+            fe += [data.x.between(seq_x[i], seq_x[i+1], inclusive='right').sum()]
+    calc_df['fe'] = fe
+
+    ft = prob_class * n
+    calc_df['ft'] = [ft]*k
+
+    if dist.lower() in ['normal', 'gumbel', 'lognormal']:
+        dk = _calc_dk(k, 2)
+    elif dist.lower() in ['logpearson3']:
+        # di buku soetopo nilai m nya diberi angka 3
+        dk = _calc_dk(k, 2)
+
+    X_calc = np.sum(np.power(2, (calc_df.fe-calc_df.ft))/calc_df.ft)
+    X_critical = calc_xcr(alpha=alpha, dk=dk, source=source_xcr)
+    result = int(X_calc < X_critical)
+    result_text = ['Distribusi Tidak Diterima', 'Distribusi Diterima']
+    calc_df.set_index('no', inplace=True)
+
+    if show_stat:
+        print(f'Periksa Kecocokan Distribusi {dist.title()}')
+        print(f'Jumlah Kelas = {k}')
+        print(f'Dk = {dk}')
+        print(f'X^2_hitungan = {X_calc:.3f}')
+        print(f'X^2_kritis = {X_critical:.3f}')
+        print(f'Result (X2_calc < X2_cr) = {result_text[result]}')
+
+    return calc_df
diff --git a/hidrokit/contrib/taruma/hk151.py b/hidrokit/contrib/taruma/hk151.py
new file mode 100644
index 0000000..7d25012
--- /dev/null
+++ b/hidrokit/contrib/taruma/hk151.py
@@ -0,0 +1,97 @@
+"""manual:
+https://gist.github.com/taruma/7bf2e4e1601ab8390d9919043eb87682"""
+
+import numpy as np
+import pandas as pd
+
+_N = np.array(
+    [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 
+     28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 
+     46, 47, 48, 49, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 110, 120, 
+     130, 140]
+)
+
+_Kn = np.array(
+    [2.036, 2.088, 2.134, 2.175, 2.213, 2.247, 2.279, 2.309, 2.335, 2.361, 
+     2.385, 2.408, 2.429, 2.448, 2.467, 2.486, 2.502, 2.519, 2.534, 2.549, 
+     2.563, 2.577, 2.591, 2.604, 2.616, 2.628, 2.639, 2.65, 2.661, 2.671, 
+     2.682, 2.692, 2.7, 2.71, 2.719, 2.727, 2.736, 2.744, 2.753, 2.76, 
+     2.768, 2.804, 2.837, 2.866, 2.893, 2.917, 2.94, 2.961, 2.981, 3, 
+     3.017, 3.049, 3.078, 3.104, 3.129]
+)
+
+t_rel_Kn_n = pd.DataFrame(np.stack((_N, _Kn), axis=1), columns=['N', 'Kn'])
+
+def find_Kn(n, table=t_rel_Kn_n):
+    if n < 10 or n > 140:
+        raise ValueError('Jumlah data diluar batas bawah (10) / batas atas (140)')
+    else:
+        N = table['N'].to_numpy()
+        Kn = table['Kn'].to_numpy()
+        return np.interp(n, N, Kn)
+
+def calc_boundary(df, col=None, result='value', show_stat=False):
+    col = df.columns[0] if col is None else col
+    
+    x = df[col].to_numpy()
+    n = x.size
+    xlog = np.log10(x)
+    xlogmean = xlog.mean()
+    xlogstd = xlog.std(ddof=1)
+
+    Kn = find_Kn(n)
+
+    # higher
+    y_h = xlogmean + Kn*xlogstd
+    val_h = 10**y_h
+
+    # lower
+    y_l = xlogmean - Kn*xlogstd
+    val_l = 10**y_l
+
+    if show_stat:
+        print(
+            f'Statistik:',
+            f'N = {n}',
+            f'Mean (log) = {xlogmean:.5f}',
+            f'Std (log) = {xlogstd:.5f}',
+            f'Lower (val) = {val_l:.5f}',
+            f'Higher (val) = {val_h:.5f}',
+            sep='\n', end='\n\n'
+        )
+
+    if result.lower() == 'value':
+        return (val_l, val_h)
+    elif result.lower() == 'log':
+        return (y_l, y_h)
+
+def find_outlier(df, col=None, verbose=False, **kwargs):
+    
+    low, high = calc_boundary(df, col, **kwargs)
+    
+    col = df.columns[0] if col is None else col
+
+    masklow = df[col] < low
+    maskhigh = df[col] > high
+    mask = masklow | maskhigh
+    
+    if verbose and masklow.sum():
+        print(f'Ada outlier dibawah batas bawah sebanyak {masklow.sum()}.')
+    if verbose and maskhigh.sum():
+        print(f'Ada outlier diatas batas atas sebanyak {maskhigh.sum()}.')
+
+    def check_outlier(x):
+        if x < low:
+            return "lower"
+        elif x > high:
+            return "higher"
+        else:
+            return pd.NA
+
+    if mask.sum() != 0:
+        new_df = df.copy()
+        new_df['outlier'] = df[col].apply(check_outlier)
+        return new_df[[col, 'outlier']]
+    else:
+        print("Tidak ada Outlier")
+        return None
diff --git a/hidrokit/contrib/taruma/hk158.py b/hidrokit/contrib/taruma/hk158.py
new file mode 100644
index 0000000..392627d
--- /dev/null
+++ b/hidrokit/contrib/taruma/hk158.py
@@ -0,0 +1,58 @@
+"""manual:
+https://gist.github.com/taruma/6a0b0f9dd26359f6832fe12bab30fdc7"""
+
+import numpy as np
+import pandas as pd
+
+def _var(x):
+    n = x.size
+    return ((x-x.mean())**2).sum()/(n-1)
+
+def _std(x):
+    return np.sqrt(_var(x))
+
+def _momen(x, r):
+    n = x.size
+    return 1/n * ((x-x.mean())**r).sum()
+
+def _skew(x):
+    n = x.size
+    return n**2 / ((n-1)*(n-2)) * _momen(x, 3) / _std(x)**3
+
+def _kurt(x):
+    n = x.size
+    return n**3 / ((n-1)*(n-2)*(n-3)) * _momen(x, 4) / _std(x)**4
+
+def _Cv(x):
+    return _std(x) / x.mean()
+
+def calc_coef(x):
+    """Return (Cv, Cs, Ck)"""
+    return (_Cv(x), _skew(x), _kurt(x))
+
+def check_distribution(x, show_stat=False):
+
+    Cv, Cs, Ck = calc_coef(x)
+
+    if show_stat:
+        print(
+            f'Cv = {Cv:.5f}',
+            f'Cs = {Cs:.5f}',
+            f'Ck = {Ck:.5f}',
+            sep='\n', end='\n'
+        )
+
+    b_normal = True if np.isclose(Cs, 0, atol=0.1) and np.isclose(Ck, 3, atol=0.1) else False
+    b_lognormal = True if np.isclose(Cs, 3, atol=0.1) and np.greater(Cs, 0) and np.isclose(Cs, 3*Cv, atol=0.1) else False
+    b_gumbel = True if np.isclose(Cs, 1.1396, atol=0.0001) and np.isclose(Ck, 5.4002, atol=0.0001) else False
+    b_logpearson = True #selalu benar terlepas nilai Cv, Cs, Ck
+
+    _kriteria = lambda x: "Memenuhi" if x else "Tidak Memenuhi"
+
+    print(
+        f'{"Normal":<20}: {_kriteria(b_normal)}',
+        f'{"Log Normal":<20}: {_kriteria(b_lognormal)}',
+        f'{"Gumbel Tipe I":<20}: {_kriteria(b_gumbel)}',
+        f'{"Log Pearson Tipe III":<20}: {_kriteria(b_logpearson)}',
+        sep='\n', end='\n'
+    )
diff --git a/hidrokit/contrib/taruma/hk172.py b/hidrokit/contrib/taruma/hk172.py
new file mode 100644
index 0000000..741e250
--- /dev/null
+++ b/hidrokit/contrib/taruma/hk172.py
@@ -0,0 +1,98 @@
+"""manual:
+https://gist.github.com/taruma/91b9fcd8fb92c12f4ea2639320ead116"""
+
+import numpy as np
+import pandas as pd
+from scipy import stats
+
+# Tabel Nilai Variabel Reduksi Gauss
+# Dari buku hidrologi: Aplikasi Metode Statistik untuk Analisa Data. hal.119
+
+# KODE: SW
+
+_DATA_SW = [
+    [1.001, 0.999, -3.050],
+    [1.005, 0.995, -2.580],
+    [1.010, 0.990, -2.330],
+    [1.050, 0.950, -1.640],
+    [1.110, 0.900, -1.280],
+    [1.250, 0.800, -0.840],
+    [1.330, 0.750, -0.670],
+    [1.430, 0.700, -0.520],
+    [1.670, 0.600, -0.250],
+    [2.000, 0.500, 0.000],
+    [2.500, 0.400, 0.250],
+    [3.330, 0.300, 0.520],
+    [4.000, 0.250, 0.670],
+    [5.000, 0.200, 0.840],
+    [10.000, 0.100, 1.280],
+    [20.000, 0.050, 1.640],
+    [50.000, 0.200, 2.050],
+    [100.000, 0.010, 2.330],
+    [200.000, 0.005, 2.580],
+    [500.000, 0.002, 2.880],
+    [1000.000, 0.001, 3.090],
+]
+
+_COL_SW = ['periode_ulang', 'peluang', 'k']
+
+t_normal_sw = pd.DataFrame(
+    data=_DATA_SW, columns=_COL_SW
+)
+
+def _find_k_in_table(return_period, table):
+    x = table.periode_ulang
+    y = table.k
+    return np.interp(return_period, x, y)
+
+def find_K(return_period, source='scipy'):
+    if source.lower() == 'soewarno':
+        return _find_k_in_table(return_period, t_normal_sw)
+    elif source.lower() == 'scipy':
+        return_period = np.array(return_period)
+        return stats.norm.ppf(1 - 1/return_period)
+
+def calc_x_normal(x, return_period=[5], source='scipy', show_stat=False):
+    return_period = np.array(return_period)
+    x = np.array(x)
+    x_mean = np.mean(x)
+    x_std = np.std(x, ddof=1)
+    n = x.size
+
+    k = find_K(return_period, source=source)
+
+    if show_stat:
+        print(f'x_mean = {x_mean:.5f}')
+        print(f'x_std = {x_std:.5f}')
+        print(f'k = {k}')
+
+    val_x = x_mean + k * x_std
+    return val_x
+
+def freq_normal(
+    df, col=None,
+    return_period=[2, 5, 10, 20, 25, 50, 100], show_stat=False, source='scipy',
+    col_name='Normal'):
+
+    col = df.columns[0] if col is None else col
+
+    x = df[col].copy()
+
+    arr = calc_x_normal(
+        x, return_period=return_period, show_stat=show_stat, source=source)
+
+    return pd.DataFrame(
+        data=arr, index=return_period, columns=[col_name]
+    )
+
+def _calc_prob_in_table(k, table):
+    x = table.k
+    y = table.peluang
+    return np.interp(k, x, y)
+
+def calc_prob(k, source='scipy'):
+    if source.lower() == 'soewarno':
+        k = np.array(k)
+        return 1 - _calc_prob_in_table(k, t_normal_sw)
+    elif source.lower() == 'scipy':
+        return stats.norm.cdf(k)
diff --git a/hidrokit/contrib/taruma/hk88.py b/hidrokit/contrib/taruma/hk88.py
index 1db9ffb..a9b51b1 100644
--- a/hidrokit/contrib/taruma/hk88.py
+++ b/hidrokit/contrib/taruma/hk88.py
@@ -51,16 +51,25 @@ def _data_from_sheet(df, station_name, as_df=True):
         return frames
 
 
-def read_workbook(io, stations, as_df=True):
-    """Read dataset from single file based on stations"""
+def read_workbook(io, stations=None, ignore_str='_', as_df=True):
+    """Read dataset from workbook"""
     excel = pd.ExcelFile(io)
 
-    data = []
+    data = {}
+    sheet_names = excel.sheet_names
+    if stations is None:
+        stations = []
+        for sheet in sheet_names:
+            if not sheet.startswith(ignore_str):
+                stations.append(sheet)
+    else:
+        stations = [stations] if isinstance(stations, str) else stations
+
     for station in stations:
-        df = pd.read_excel(excel, sheet_name=station, header=None)
-        data.append(_data_from_sheet(df, station))
+        df = excel.parse(sheet_name=station, header=None)
+        data[station] = _data_from_sheet(df, station)
 
     if as_df:
-        return pd.concat(data, sort=True, axis=1)
+        return pd.concat(data.values(), sort=True, axis=1)
     else:
         return data
diff --git a/hidrokit/contrib/taruma/hk98.py b/hidrokit/contrib/taruma/hk98.py
index bf35b8d..6c58d64 100644
--- a/hidrokit/contrib/taruma/hk98.py
+++ b/hidrokit/contrib/taruma/hk98.py
@@ -27,14 +27,15 @@ def summary_station(dataset, column, ufunc, ufunc_col, n_days='MS'):
     )
 
 
-def summary_all(dataset, ufunc, ufunc_col, columns=None, n_days='MS'):
+def summary_all(dataset, ufunc, ufunc_col, columns=None, n_days='MS', verbose=False):
     res = []
 
     columns = columns if columns is not None else list(dataset.columns)
     columns = columns if isinstance(columns, (list, tuple)) else [columns]
 
     for column in columns:
-        print('PROCESSING:', column)
+        if verbose:
+            print('PROCESSING:', column)
         res.append(
             summary_station(dataset, column, ufunc, ufunc_col, n_days=n_days)
         )
diff --git a/requirements.txt b/requirements.txt
index 0a8218e..315e10c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,4 @@ pandas
 numpy
 matplotlib
 xlrd
+scipy
diff --git a/setup.py b/setup.py
index 1cf2ef5..a4a4ec1 100644
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,7 @@
+"""
+Setup files
+"""
+
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
@@ -11,9 +15,9 @@
 NAME = 'hidrokit'
 DESCRIPTION = 'analisis hidrologi dengan python'
 URL = 'https://github.com/hidrokit/hidrokit'
-EMAIL = 'timhidrokit@gmail.com'
-AUTHOR = 'Hidrokit'
-REQUIRES_PYTHON = '>=3.5.0'
+EMAIL = 'hi@taruma.info'
+AUTHOR = 'Taruma Sakti Megariansyah'
+REQUIRES_PYTHON = '>=3.6.0'
 VERSION = ''
 
 # What packages are required for this module to be executed?
@@ -90,7 +94,6 @@
         # Python Version
         'Programming Language :: Python',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
     ],


Test patch:
diff --git a/tests/test_prep_excel.py b/tests/test_prep_excel.py
index 38fa3a6..28a69a8 100644
--- a/tests/test_prep_excel.py
+++ b/tests/test_prep_excel.py
@@ -64,5 +64,5 @@ def test__dataframe_table():
     result = pd.read_csv('tests/data/2013 DUGAAIR.csv',
                          index_col=0, parse_dates=True)
     assert_frame_equal(
-        result, dataframe
+        result, dataframe, check_like=True
     )