├── .gitignore
├── .travis.yml
├── LICENSE
├── MANIFEST
├── MANIFEST.in
├── README
├── README.md
├── ci
├── .travis_install.sh
└── .travis_test.sh
├── docs
└── images
│ └── pyclust-logo-tentative.png
├── examples
├── .ipynb_checkpoints
│ ├── bisecting_kmeans-checkpoint.ipynb
│ ├── comp_KM_GMM-checkpoint.ipynb
│ ├── compare_kernelKmenas_vs_kmeans-checkpoint.ipynb
│ └── kmedoids-checkpoint.ipynb
├── bisecting_kmeans.ipynb
├── compare_GMM_vs_KM.ipynb
├── compare_KM_vs_BKM.ipynb
├── compare_kernelKmenas_vs_kmeans.ipynb
├── data
│ ├── data_k5.csv
│ └── data_k5.txt
└── kmedoids.ipynb
├── pyclust
├── __init__.py
├── _bisect_kmeans.py
├── _gaussian_mixture_model.py
├── _kernel_kmeans.py
├── _kmeans.py
├── _kmedoids.py
└── validate
│ ├── __init__.py
│ └── _internal.py
├── requirements.txt
├── setup.py
└── tests
├── test_bi_kmeans.py
├── test_gmm.py
├── test_kernel_kmeans.py
├── test_kmeans.py
├── test_kmedoids.py
└── test_silhouette.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 |
46 | # Translations
47 | *.mo
48 | *.pot
49 |
50 | # Django stuff:
51 | *.log
52 |
53 | # Sphinx documentation
54 | docs/_build/
55 |
56 | # PyBuilder
57 | target/
58 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | virtualenv:
3 | system_site_packages: true
4 | env:
5 | matrix:
6 | - PYTHON_VERSION="2.7" COVERAGE="true" LATEST="true"
7 | - PYTHON_VERSION="2.7" NUMPY_VERSION="1.9.2" SCIPY_VERSION="0.15.1" SKLEARN_VERSION="0.16.1" PANDAS_VERSION="0.16.0" MATPLOTLIB_VERSION="1.4.3"
8 | - PYTHON_VERSION="3.4" LATEST="true"
9 | install: source ./ci/.travis_install.sh
10 | script: bash ./ci/.travis_test.sh
11 | after_success:
12 | # Ignore coveralls failures as the coveralls server is not very reliable
13 | # but we don't want travis to report a failure in the github UI just
14 | # because the coverage report failed to be published.
15 | - if [[ "$COVERAGE" == "true" ]]; then coveralls || echo "failed"; fi
16 | cache: apt
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/MANIFEST:
--------------------------------------------------------------------------------
1 | # file GENERATED by distutils, do NOT edit
2 | README
3 | README.md
4 | requirements.txt
5 | setup.py
6 | examples/bisecting_kmeans.ipynb
7 | examples/compare_GMM_vs_KM.ipynb
8 | examples/compare_KM_vs_BKM.ipynb
9 | examples/compare_kernelKmenas_vs_kmeans.ipynb
10 | examples/kmedoids.ipynb
11 | pyclust/__init__.py
12 | pyclust/_bisect_kmeans.py
13 | pyclust/_gaussian_mixture_model.py
14 | pyclust/_kernel_kmeans.py
15 | pyclust/_kmeans.py
16 | pyclust/_kmedoids.py
17 | pyclust/validate/__init__.py
18 | pyclust/validate/_internal.py
19 | tests/test_bi_kmeans.py
20 | tests/test_gmm.py
21 | tests/test_kernel_kmeans.py
22 | tests/test_kmeans.py
23 | tests/test_kmedoids.py
24 | tests/test_silhouette.py
25 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md requirements.txt
2 | include examples/*.ipynb
3 | recursive-include pyclust *.py
4 | recursive-include tests *.py
5 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | pyclust
2 | ========
3 |
4 | Clustering Algorithms in Python
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](https://travis-ci.org/mirjalil/pyclust)
3 | [](https://landscape.io/github/mirjalil/pyclust)
4 | [](http://badge.fury.io/py/pyclust)
5 |
6 |
7 | 
8 |
9 |
10 | Documentation: http://mirjalil.github.io/pyclust
11 |
12 |
--------------------------------------------------------------------------------
/ci/.travis_install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # This script is meant to be called by the "install" step defined in
3 | # .travis.yml. See http://docs.travis-ci.com/ for more details.
4 | # The behavior of the script is controlled by environment variabled defined
5 | # in the .travis.yml in the top level folder of the project.
6 |
7 | # License: 3-clause BSD
8 |
9 | set -e
10 |
11 | # Fix the compilers to workaround avoid having the Python 3.4 build
12 | # lookup for g++44 unexpectedly.
13 | export CC=gcc
14 | export CXX=g++
15 |
16 | # Deactivate the travis-provided virtual environment and setup a
17 | # conda-based environment instead
18 | deactivate
19 |
20 | # Use the miniconda installer for faster download / install of conda
21 | # itself
22 | wget http://repo.continuum.io/miniconda/Miniconda-3.7.0-Linux-x86_64.sh \
23 | -O miniconda.sh
24 | chmod +x miniconda.sh && ./miniconda.sh -b
25 | export PATH=/home/travis/miniconda/bin:$PATH
26 | conda update --yes conda
27 |
28 | # Configure the conda environment and put it in the path using the
29 | # provided versions
30 | if [[ "$LATEST" == "true" ]]; then
31 | conda create -n testenv --yes python=$PYTHON_VERSION pip nose \
32 | numpy scipy scikit-learn cython pandas matplotlib
33 | else
34 | conda create -n testenv --yes python=$PYTHON_VERSION pip nose \
35 | numpy=$NUMPY_VERSION scipy=$SCIPY_VERSION \
36 | scikit-learn=$SKLEARN_VERSION \
37 | pandas=$PANDAS_VERSION \
38 | matplotlib=$MATPLOTLIB_VERSION cython
39 | fi
40 |
41 | source activate testenv
42 |
43 | if [[ "$COVERAGE" == "true" ]]; then
44 | pip install coverage coveralls
45 | fi
46 |
47 |
48 | pip install treelib
49 |
50 | # Build gplearn in the install.sh script to collapse the verbose
51 | # build output in the travis output when it succeeds.
52 | python --version
53 | python -c "import numpy; print('numpy %s' % numpy.__version__)"
54 | python -c "import scipy; print('scipy %s' % scipy.__version__)"
55 | python -c "import sklearn; print('sklearn %s' % sklearn.__version__)"
56 | python -c "import pandas; print('pandas %s' % pandas.__version__)"
57 | python -c "import matplotlib; print('matplotlib %s' % matplotlib.__version__)"
58 | python setup.py build_ext --inplace
59 |
--------------------------------------------------------------------------------
/ci/.travis_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # This script is meant to be called by the "script" step defined in
3 | # .travis.yml. See http://docs.travis-ci.com/ for more details.
4 | # The behavior of the script is controlled by environment variabled defined
5 | # in the .travis.yml in the top level folder of the project.
6 |
7 | # License: 3-clause BSD
8 |
9 | set -e
10 |
11 | python --version
12 | python -c "import numpy; print('numpy %s' % numpy.__version__)"
13 | python -c "import scipy; print('scipy %s' % scipy.__version__)"
14 | python -c "import sklearn; print('sklearn %s' % sklearn.__version__)"
15 | python -c "import pandas; print('pandas %s' % pandas.__version__)"
16 | python -c "import matplotlib; print('matplotlib %s' % matplotlib.__version__)"
17 |
18 | if [[ "$COVERAGE" == "true" ]]; then
19 | nosetests -s -v --with-coverage --cover-package=tests/test_gmm.py
20 | else
21 | nosetests -s -v tests/test_gmm.py
22 | fi
23 | #make test-doc test-sphinxext
24 |
--------------------------------------------------------------------------------
/docs/images/pyclust-logo-tentative.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vmirly/pyclust/bdb12be4649e70c6c90da2605bc5f4b314e2d07e/docs/images/pyclust-logo-tentative.png
--------------------------------------------------------------------------------
/examples/data/data_k5.csv:
--------------------------------------------------------------------------------
1 | x,y,label
2 | 9.3716,-0.4233,4
3 | 10.1449,0.0706,4
4 | -9.7006,4.7697,1
5 | -9.1902,5.4692,1
6 | 7.0868,1.2443,5
7 | -8.8987,6.2341,1
8 | -5.9528,2.9749,2
9 | -11.1316,4.2041,1
10 | -5.0696,5.0548,2
11 | -10.3145,4.4601,1
12 | -9.6497,5.9251,1
13 | -11.1016,-1.1102,3
14 | -10.7579,4.7315,1
15 | -7.0165,4.1340,2
16 | -6.5479,2.4876,2
17 | 11.2710,-0.9987,4
18 | -6.6153,1.7544,2
19 | -9.5631,4.7270,1
20 | -10.0955,3.8709,1
21 | -9.6523,4.7177,1
22 | 9.7840,-0.7195,4
23 | -9.9356,4.7251,1
24 | -9.0083,-2.4628,3
25 | -9.8144,-2.1312,3
26 | -7.7955,1.6824,2
27 | -9.4895,-2.1914,3
28 | -7.5306,0.3740,3
29 | -7.0595,-1.6496,3
30 | 5.8198,0.1317,5
31 | 6.7902,0.7076,5
32 | -10.5252,-3.0888,3
33 | 10.0502,-0.8898,4
34 | -8.6586,-2.4603,3
35 | 10.1325,-0.6966,4
36 | -12.0514,4.4795,1
37 | 10.5645,-0.5806,4
38 | -5.5934,1.2156,2
39 | 8.9211,-0.9387,4
40 | -8.7295,-1.2907,3
41 | -6.5833,2.1517,2
42 | -10.2950,-1.4051,3
43 | -6.3471,2.2745,2
44 | -6.2304,3.6193,2
45 | 6.5262,0.4026,5
46 | -8.5803,-1.7069,3
47 | -9.5816,4.6367,1
48 | -7.0934,2.7715,2
49 | 8.7214,-1.6531,4
50 | -7.3329,1.9659,2
51 | 9.2268,-1.3251,4
52 | 9.2166,-1.5321,4
53 | -10.7638,4.2417,1
54 | -5.2805,4.2142,2
55 | 7.5874,0.2566,5
56 | -9.3804,-2.2324,3
57 | -9.2143,-1.8715,3
58 | -9.8484,5.5779,1
59 | 8.2346,1.7254,5
60 | 10.9436,-0.7238,4
61 | 9.6885,-0.7281,4
62 | -10.2664,-1.9866,3
63 | -8.8969,-3.1654,3
64 | -9.5047,4.7427,1
65 | -9.4616,5.1830,1
66 | 8.6615,-1.9847,4
67 | -8.8223,6.9551,1
68 | -6.7701,5.4426,2
69 | -7.4205,3.5070,2
70 | -8.0293,-2.7058,3
71 | 6.9737,0.9576,5
72 | -8.9639,-1.5780,3
73 | -9.5229,7.2620,1
74 | -6.7140,3.0260,2
75 | 10.3461,0.1801,4
76 | -9.3876,-1.6806,3
77 | -10.1113,4.8976,1
78 | -8.8818,6.0297,1
79 | -9.1809,-1.8762,3
80 | -10.7872,5.3637,1
81 | -10.1538,-1.9913,3
82 | -9.1101,-2.2972,3
83 | -7.2095,1.7404,2
84 | -9.8625,-1.9846,3
85 | -9.6910,-3.6916,3
86 | -8.2382,3.4500,2
87 | -8.2104,-1.9683,3
88 | -9.0501,6.5029,1
89 | -9.4937,4.7799,1
90 | -6.8546,3.2207,2
91 | -10.7498,6.9710,1
92 | -7.4671,-1.0552,3
93 | -6.6878,1.5284,2
94 | -9.3081,7.4783,1
95 | -5.8355,4.1978,2
96 | -5.8320,3.5339,2
97 | -9.4614,-0.3982,3
98 | 10.0967,-1.1197,4
99 | 6.8396,0.0096,5
100 | -5.5235,2.6243,2
101 | -9.3381,-0.1420,3
102 | -6.3811,3.3233,2
103 | 10.0117,-1.2745,4
104 | -9.2842,4.3292,1
105 | -5.3863,3.4672,2
106 | -7.0557,2.8807,2
107 | -9.2487,5.0036,1
108 | -5.8832,4.6558,2
109 | -10.2913,-2.2978,3
110 | 10.4775,-1.1031,4
111 | 6.4062,1.2880,5
112 | 4.8806,0.0357,5
113 | -6.7634,2.2748,2
114 | -10.2162,3.9053,1
115 | -8.8327,-3.9787,3
116 | 10.0621,1.0644,4
117 | -6.4314,2.9813,2
118 | -6.3881,3.6334,2
119 | -9.7080,5.7542,1
120 | 11.3255,-1.0922,4
121 | -9.2550,-2.8646,3
122 |
--------------------------------------------------------------------------------
/examples/data/data_k5.txt:
--------------------------------------------------------------------------------
1 | x1 x2 label
2 | -4.4791 3.3937 2
3 | 7.8984 -5.8965 3
4 | -9.8639 -7.7490 0
5 | 2.9518 -5.1833 3
6 | -4.1867 3.8343 2
7 | -3.1768 4.3702 2
8 | 5.4088 -5.3447 3
9 | -9.5431 4.6309 1
10 | 4.3218 -5.5438 3
11 | 5.4968 -5.6538 3
12 | -10.6395 4.6339 1
13 | 3.9048 -4.6311 3
14 | -4.8774 -2.4898 4
15 | 4.3310 -6.7243 3
16 | -3.2520 3.2348 2
17 | -4.0318 3.4640 2
18 | 4.9453 -6.1662 3
19 | 5.7822 -5.4181 3
20 | 5.6520 -5.1689 3
21 | -4.6943 -3.2294 4
22 | 4.4173 -5.2402 3
23 | -4.0492 3.1254 2
24 | -8.1458 -8.0735 0
25 | -9.2024 3.5654 1
26 | -8.8864 3.8588 1
27 | -10.0461 -6.8617 0
28 | -3.1254 4.2010 2
29 | 6.1868 -5.9088 3
30 | -10.0851 -7.8282 0
31 | -10.0197 -8.3914 0
32 | -4.2128 -3.3652 4
33 | -4.1858 -2.8936 4
34 | -3.7368 -1.6311 4
35 | -6.1987 -3.8387 4
36 | -10.2987 -8.3270 0
37 | -5.0460 -3.1392 4
38 | -11.4083 4.0554 1
39 | -7.1037 -2.2672 4
40 | -4.9485 2.5051 2
41 | -10.5968 2.9082 1
42 | -3.1611 3.0290 2
43 | -10.7422 3.0163 1
44 | -6.2223 -4.5978 4
45 | -4.6604 3.5029 2
46 | -7.4190 -2.1297 4
47 | -4.7785 3.3931 2
48 | -10.6990 4.5211 1
49 | 4.0904 -5.7173 3
50 | 3.4097 -6.2113 3
51 | -8.7725 -8.7656 0
52 | 4.3718 -5.9135 3
53 | -8.6801 5.4390 1
54 | -4.8013 -5.8405 4
55 | 5.4612 -5.6356 3
56 | -5.4997 -4.3304 4
57 | -9.4577 -9.0326 0
58 | -5.5435 -4.2081 4
59 | -4.3186 3.8059 2
60 | -8.6597 -6.3829 0
61 | -9.9956 3.0550 1
62 | 6.3597 -5.9405 3
63 | -3.6365 2.3814 2
64 | -10.5232 4.1920 1
65 | -7.6656 -8.0195 0
66 | -4.0457 4.8850 2
67 | -4.3781 -4.7447 4
68 | -9.7517 -9.0156 0
69 | -9.2809 4.9565 1
70 | 3.9760 -4.9489 3
71 | -6.2830 -3.9347 4
72 | 5.0330 -6.1761 3
73 | -9.7813 -9.2572 0
74 | -11.0315 3.6441 1
75 | 5.5258 -5.1673 3
76 | -11.4239 4.6926 1
77 | -5.0557 -2.9554 4
78 | -9.0357 5.9764 1
79 | 5.6075 -5.9669 3
80 | 6.6010 -5.6611 3
81 | -10.2395 4.9487 1
82 | -8.8909 -8.1023 0
83 | -10.3803 4.0075 1
84 | -5.0648 -3.8558 4
85 | -7.3378 -2.3940 4
86 | -9.8274 3.2856 1
87 | -7.7716 -8.6099 0
88 | -9.5704 -10.1851 0
89 | -2.8091 5.2364 2
90 | -3.7752 3.2464 2
91 | -4.2066 -6.1067 4
92 | -4.9114 -3.4216 4
93 | -4.4552 -3.5057 4
94 | -9.1961 4.0429 1
95 | -8.1431 4.0263 1
96 | -9.0886 -8.6273 0
97 | -11.0169 3.4889 1
98 | 5.6792 -5.1388 3
99 | 4.8909 -5.8556 3
100 | 4.7322 -4.0895 3
101 | -9.5160 -7.5866 0
102 | 5.4398 -5.9188 3
103 | -5.2242 -0.0478 4
104 | -8.6125 -7.4585 0
105 | -9.3216 -9.4631 0
106 | -9.7942 -7.3977 0
107 | -3.8286 2.5118 2
108 | -9.2315 -8.0868 0
109 | -2.8642 5.3891 2
110 | -10.9938 4.6405 1
111 | -5.3267 3.7716 2
112 | -4.2468 2.3606 2
113 | -8.7130 -8.3648 0
114 | -8.8329 -8.8046 0
115 | -9.2127 3.2399 1
116 | -6.2019 -4.6130 4
117 | -9.6228 -6.3125 0
118 | -10.6627 6.4633 1
119 | -4.3118 -3.5116 4
120 | -8.3277 4.2822 1
121 | -8.2551 -7.0715 0
122 | 2.9148 -6.8599 3
123 | -9.9834 -9.6528 0
124 | -2.9007 4.3563 2
125 | -9.6864 -7.8579 0
126 | 5.4794 -7.0687 3
127 | -9.8626 5.3823 1
128 | -4.7216 4.4998 2
129 | -7.3743 -7.8252 0
130 | 6.6317 -5.9392 3
131 | 4.4958 -4.6014 3
132 | -9.3933 -8.9807 0
133 | -11.1569 4.7187 1
134 | 5.1866 -6.6096 3
135 | -9.0050 4.1887 1
136 | -4.9271 -4.6270 4
137 | -8.4499 3.7425 1
138 | -10.4434 4.4453 1
139 | 5.3197 -5.7306 3
140 | -5.6644 -3.7406 4
141 | -4.4926 2.9934 2
142 | -4.3424 -4.5848 4
143 | -9.1353 4.7492 1
144 | -4.5283 -4.8782 4
145 | -3.2452 2.6036 2
146 | 5.9346 -6.2940 3
147 | -4.3771 4.8473 2
148 | -3.8048 3.3566 2
149 | -8.3716 -8.0255 0
150 | 5.3566 -7.2207 3
151 | -7.5255 -7.4856 0
152 | -5.5182 -3.8333 4
153 | -4.9309 3.4471 2
154 | -4.0898 3.7194 2
155 | -9.0815 -8.2778 0
156 | 4.3885 -7.0471 3
157 | -6.7164 -4.1545 4
158 | -4.6582 -2.8804 4
159 | 6.5552 -3.4784 3
160 | -10.0585 -7.3565 0
161 | -8.6341 5.8623 1
162 | -10.1693 -7.0268 0
163 | -9.7052 5.3153 1
164 | -9.2413 -9.9978 0
165 | -6.8781 -5.2769 4
166 | -10.5360 4.8153 1
167 | -5.2178 2.5060 2
168 | -5.1568 -2.2503 4
169 | -8.8603 -9.1659 0
170 | 6.1220 -5.9961 3
171 | -4.0345 3.6847 2
172 | 6.5271 -5.0738 3
173 | -4.9300 3.7007 2
174 | -5.6830 2.6654 2
175 | 5.1247 -4.9710 3
176 | -3.2428 2.4205 2
177 | -8.8656 -6.9812 0
178 | -9.6682 4.8939 1
179 | -3.6967 3.5237 2
180 | -9.1200 -9.5740 0
181 | -8.8484 -8.9083 0
182 | -9.5319 3.8516 1
183 | -9.8538 -9.8689 0
184 | -2.7827 4.7249 2
185 | -5.0561 -3.5068 4
186 | -8.8758 4.0565 1
187 | -8.8767 5.4792 1
188 | -5.4080 -4.6670 4
189 | -6.1483 -4.4859 4
190 | 3.3675 -4.6395 3
191 | -5.2386 2.4540 2
192 | -4.1245 -1.6710 4
193 | -4.8915 3.2353 2
194 | -6.6277 2.0789 2
195 | -4.0553 4.1504 2
196 | -4.7384 -2.4468 4
197 | -6.1181 -2.7184 4
198 | -8.8202 5.4299 1
199 | -3.4339 3.4155 2
200 | -8.3427 5.1111 1
201 | -5.2566 -2.2820 4
202 |
--------------------------------------------------------------------------------
/examples/kmedoids.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {
7 | "collapsed": true
8 | },
9 | "outputs": [],
10 | "source": [
11 | "import numpy as np\n",
12 | "import scipy\n",
13 | "import pandas\n",
14 | "import treelib\n",
15 | "import pyclust\n",
16 | "import pandas\n",
17 | "\n",
18 | "import matplotlib\n",
19 | "import matplotlib.pyplot as plt\n",
20 | "%matplotlib inline"
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": 2,
26 | "metadata": {
27 | "collapsed": false
28 | },
29 | "outputs": [
30 | {
31 | "data": {
32 | "text/plain": [
33 | "'0.1.1'"
34 | ]
35 | },
36 | "execution_count": 2,
37 | "metadata": {},
38 | "output_type": "execute_result"
39 | }
40 | ],
41 | "source": [
42 | "pyclust.__version__"
43 | ]
44 | },
45 | {
46 | "cell_type": "code",
47 | "execution_count": 3,
48 | "metadata": {
49 | "collapsed": true
50 | },
51 | "outputs": [],
52 | "source": [
53 | "def plot_scatter(X, labels=None, centers=None, title=\"Scatter Plot\"):\n",
54 | " \n",
55 | " labels = np.zeros(shape=X.shape[0], dtype=int) if labels is None else labels\n",
56 | " colors = ['b', 'r', 'g', 'm', 'y']\n",
57 | " col_dict = {}\n",
58 | " i = 0\n",
59 | " for lab in np.unique(labels):\n",
60 | " col_dict[lab] = colors[i]\n",
61 | " i += 1 \n",
62 | " \n",
63 | " fig1 = plt.figure(1, figsize=(8,6))\n",
64 | " ax = fig1.add_subplot(1, 1, 1)\n",
65 | "\n",
66 | " for i in np.unique(labels):\n",
67 | " indx = np.where(labels == i)[0]\n",
68 | " plt.scatter(X[indx,0], X[indx,1], color=col_dict[i], marker='o', s=100, alpha=0.5)\n",
69 | "\n",
70 | " if centers is not None:\n",
71 | " plt.scatter(centers[:,0], centers[:,1], color='magenta', marker='*', s=250, alpha=0.5)\n",
72 | " \n",
73 | " plt.setp(ax.get_xticklabels(), rotation='horizontal', fontsize=16)\n",
74 | " plt.setp(ax.get_yticklabels(), rotation='vertical', fontsize=16)\n",
75 | "\n",
76 | " plt.xlabel('$x_1$', size=20)\n",
77 | " plt.ylabel('$x_2$', size=20)\n",
78 | " plt.title(title, size=20)\n",
79 | "\n",
80 | " plt.show()\n"
81 | ]
82 | },
83 | {
84 | "cell_type": "markdown",
85 | "metadata": {},
86 | "source": [
87 | "## Example 1"
88 | ]
89 | },
90 | {
91 | "cell_type": "code",
92 | "execution_count": 4,
93 | "metadata": {
94 | "collapsed": false
95 | },
96 | "outputs": [
97 | {
98 | "data": {
99 | "image/png": "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\nSUVORK5CYII=\n",
100 | "text/plain": [
101 | ""
102 | ]
103 | },
104 | "metadata": {},
105 | "output_type": "display_data"
106 | }
107 | ],
108 | "source": [
109 | "m1 = np.array([-2.5, 1.0])\n",
110 | "m2 = np.array([0.0, -3.0])\n",
111 | "m3 = np.array([2.0, 2.0])\n",
112 | "\n",
113 | "X1 = np.random.multivariate_normal(mean=m1, cov=np.eye(2), size=20)\n",
114 | "X2 = np.random.multivariate_normal(mean=m2, cov=np.eye(2), size=30)\n",
115 | "X3 = np.random.multivariate_normal(mean=m3, cov=np.eye(2), size=10)\n",
116 | "\n",
117 | "X = np.vstack((X1, X2, X3))\n",
118 | "\n",
119 | "indx_arr = np.arange(X.shape[0])\n",
120 | "np.random.shuffle(indx_arr)\n",
121 | "\n",
122 | "y = np.hstack((np.zeros(20, dtype=int), np.ones(30, dtype=int), 2*np.ones(10, dtype=int)))\n",
123 | "X = X[indx_arr,:]\n",
124 | "y = y[indx_arr]\n",
125 | "\n",
126 | "plot_scatter(X, labels=None, centers=None, title=\"Scatter Plot\")"
127 | ]
128 | },
129 | {
130 | "cell_type": "code",
131 | "execution_count": 5,
132 | "metadata": {
133 | "collapsed": false
134 | },
135 | "outputs": [
136 | {
137 | "data": {
138 | "image/png": "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\nihkAAAAASUVORK5CYII=\n",
139 | "text/plain": [
140 | ""
141 | ]
142 | },
143 | "metadata": {},
144 | "output_type": "display_data"
145 | }
146 | ],
147 | "source": [
148 | "## Performing KMeans\n",
149 | "kmd = pyclust.KMedoids(n_clusters=3, n_trials=50)\n",
150 | "\n",
151 | "kmd.fit(X)\n",
152 | "\n",
153 | "plot_scatter(X, labels=kmd.labels_, centers=kmd.centers_, title=\"Scatter Plot: KMeans Clustering\")"
154 | ]
155 | },
156 | {
157 | "cell_type": "markdown",
158 | "metadata": {},
159 | "source": [
160 | "## Example 2"
161 | ]
162 | },
163 | {
164 | "cell_type": "code",
165 | "execution_count": 9,
166 | "metadata": {
167 | "collapsed": false
168 | },
169 | "outputs": [
170 | {
171 | "data": {
172 | "image/png": "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\ngg==\n",
173 | "text/plain": [
174 | ""
175 | ]
176 | },
177 | "metadata": {},
178 | "output_type": "display_data"
179 | }
180 | ],
181 | "source": [
182 | "s1 = np.array([[0.3, 0.05], [0.05, 0.3]])\n",
183 | "s2 = np.array([[0.6, 0.0], [0.0, 1.1]])\n",
184 | "s3 = np.array([[0.4, -0.05], [-0.02, 0.2]])\n",
185 | "\n",
186 | "m1 = np.array([-2.0, 1.0])\n",
187 | "m2 = np.array([0.0, -3.0])\n",
188 | "m3 = np.array([1.0, 2.0])\n",
189 | "\n",
190 | "X1 = np.random.multivariate_normal(mean=m1, cov=s1, size=100)\n",
191 | "X2 = np.random.multivariate_normal(mean=m2, cov=s2, size=150)\n",
192 | "X3 = np.random.multivariate_normal(mean=m3, cov=s3, size=80)\n",
193 | "\n",
194 | "X = np.vstack((X1, X2, X3))\n",
195 | "\n",
196 | "indx_arr = np.arange(X.shape[0])\n",
197 | "np.random.shuffle(indx_arr)\n",
198 | "\n",
199 | "y = np.hstack((np.zeros(100, dtype=int), np.ones(150, dtype=int), 2*np.ones(80, dtype=int)))\n",
200 | "X = X[indx_arr,:]\n",
201 | "y = y[indx_arr]\n",
202 | "\n",
203 | "## Performing KMedoids\n",
204 | "kmd = pyclust.KMedoids(n_clusters=3, n_trials=50)\n",
205 | "\n",
206 | "kmd.fit(X)\n",
207 | "\n",
208 | "plot_scatter(X, labels=kmd.labels_, centers=kmd.centers_, title=\"Scatter Plot: KMeans Clustering\")"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": null,
214 | "metadata": {
215 | "collapsed": true
216 | },
217 | "outputs": [],
218 | "source": []
219 | }
220 | ],
221 | "metadata": {
222 | "kernelspec": {
223 | "display_name": "Python 3",
224 | "language": "python",
225 | "name": "python3"
226 | },
227 | "language_info": {
228 | "codemirror_mode": {
229 | "name": "ipython",
230 | "version": 3
231 | },
232 | "file_extension": ".py",
233 | "mimetype": "text/x-python",
234 | "name": "python",
235 | "nbconvert_exporter": "python",
236 | "pygments_lexer": "ipython3",
237 | "version": "3.4.3"
238 | }
239 | },
240 | "nbformat": 4,
241 | "nbformat_minor": 0
242 | }
243 |
--------------------------------------------------------------------------------
/pyclust/__init__.py:
--------------------------------------------------------------------------------
1 | # Vahid Mirjalili 08/07/2015
2 | # Unsupervised Data Clustering Algorithms in Python
3 | # Machine Learning Application
4 |
5 |
6 | from ._kmeans import KMeans
7 | from ._bisect_kmeans import BisectKMeans
8 | from ._gaussian_mixture_model import GMM
9 | from ._kmedoids import KMedoids
10 | from ._kernel_kmeans import KernelKMeans
11 |
12 | __version__ = '0.1.18'
13 |
--------------------------------------------------------------------------------
/pyclust/_bisect_kmeans.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import treelib
3 |
4 | from . import _kmeans
5 |
6 |
7 |
8 | def _select_cluster_2_split(membs, tree):
9 | leaf_nodes = tree.leaves()
10 | num_leaves = len(leaf_nodes)
11 | if len(leaf_nodes)>1:
12 | sse_arr = np.empty(shape=num_leaves, dtype=float)
13 | labels = np.empty(shape=num_leaves, dtype=int)
14 |
15 | for i,node in enumerate(leaf_nodes):
16 | sse_arr[i] = node.data['sse']
17 | labels[i] = node.data['label']
18 | id_max = np.argmax(sse_arr)
19 | clust_id = labels[id_max]
20 | memb_ids = np.where(membs == clust_id)[0]
21 | return(clust_id,memb_ids)
22 | else:
23 | return(0,np.arange(membs.shape[0]))
24 |
25 |
26 |
27 | def _cut_tree(tree, n_clusters, membs):
28 | """ Cut the tree to get desired number of clusters as n_clusters
29 | 2 <= n_desired <= n_clusters
30 | """
31 | ## starting from root,
32 | ## a node is added to the cut_set or
33 | ## its children are added to node_set
34 | assert(n_clusters >= 2)
35 | assert(n_clusters <= len(tree.leaves()))
36 |
37 | cut_centers = dict() #np.empty(shape=(n_clusters, ndim), dtype=float)
38 |
39 | for i in range(n_clusters-1):
40 | if i==0:
41 | search_set = set(tree.children(0))
42 | node_set,cut_set = set(), set()
43 | else:
44 | search_set = node_set.union(cut_set)
45 | node_set,cut_set = set(), set()
46 |
47 | if i+2 == n_clusters:
48 | cut_set = search_set
49 | else:
50 | for _ in range(len(search_set)):
51 | n = search_set.pop()
52 |
53 | if n.data['ilev'] is None or n.data['ilev']>i+2:
54 | cut_set.add(n)
55 | else:
56 | nid = n.identifier
57 | if n.data['ilev']-2==i:
58 | node_set = node_set.union(set(tree.children(nid)))
59 |
60 | conv_membs = membs.copy()
61 | for node in cut_set:
62 | nid = node.identifier
63 | label = node.data['label']
64 | cut_centers[label] = node.data['center']
65 | sub_leaves = tree.leaves(nid)
66 | for leaf in sub_leaves:
67 | indx = np.where(conv_membs == leaf)[0]
68 | conv_membs[indx] = nid
69 |
70 | return(conv_membs, cut_centers)
71 |
72 |
73 | def _add_tree_node(tree, label, ilev, X=None, size=None, center=None, sse=None, parent=None):
74 | """ Add a node to the tree
75 | if parent is not known, the node is a root
76 |
77 | The nodes of this tree keep properties of each cluster/subcluster:
78 | size --> cluster size as the number of points in the cluster
79 | center --> mean of the cluster
80 | label --> cluster label
81 | sse --> sum-squared-error for that single cluster
82 | ilev --> the level at which this node is split into 2 children
83 | """
84 | if size is None:
85 | size = X.shape[0]
86 | if (center is None):
87 | center = np.mean(X, axis=0)
88 | if (sse is None):
89 | sse = _kmeans._cal_dist2center(X, center)
90 |
91 | center = list(center)
92 | datadict = {
93 | 'size' : size,
94 | 'center': center,
95 | 'label' : label,
96 | 'sse' : sse,
97 | 'ilev' : None
98 | }
99 | if (parent is None):
100 | tree.create_node(label, label, data=datadict)
101 | else:
102 | tree.create_node(label, label, parent=parent, data=datadict)
103 | tree.get_node(parent).data['ilev'] = ilev
104 |
105 | return(tree)
106 |
107 |
108 |
109 |
110 | def _bisect_kmeans(X, n_clusters, n_trials, max_iter, tol):
111 | """ Apply Bisecting Kmeans clustering
112 | to reach n_clusters number of clusters
113 | """
114 | membs = np.empty(shape=X.shape[0], dtype=int)
115 | centers = dict() #np.empty(shape=(n_clusters,X.shape[1]), dtype=float)
116 | sse_arr = dict() #-1.0*np.ones(shape=n_clusters, dtype=float)
117 |
118 | ## data structure to store cluster hierarchies
119 | tree = treelib.Tree()
120 | tree = _add_tree_node(tree, 0, ilev=0, X=X)
121 |
122 | km = _kmeans.KMeans(n_clusters=2, n_trials=n_trials, max_iter=max_iter, tol=tol)
123 | for i in range(1,n_clusters):
124 | sel_clust_id,sel_memb_ids = _select_cluster_2_split(membs, tree)
125 | X_sub = X[sel_memb_ids,:]
126 | km.fit(X_sub)
127 |
128 | #print("Bisecting Step %d :"%i, sel_clust_id, km.sse_arr_, km.centers_)
129 | ## Updating the clusters & properties
130 | #sse_arr[[sel_clust_id,i]] = km.sse_arr_
131 | #centers[[sel_clust_id,i]] = km.centers_
132 | tree = _add_tree_node(tree, 2*i-1, i, \
133 | size=np.sum(km.labels_ == 0), center=km.centers_[0], \
134 | sse=km.sse_arr_[0], parent= sel_clust_id)
135 | tree = _add_tree_node(tree, 2*i, i, \
136 | size=np.sum(km.labels_ == 1), center=km.centers_[1], \
137 | sse=km.sse_arr_[1], parent= sel_clust_id)
138 |
139 | pred_labels = km.labels_
140 | pred_labels[np.where(pred_labels == 1)[0]] = 2*i
141 | pred_labels[np.where(pred_labels == 0)[0]] = 2*i - 1
142 | #if sel_clust_id == 1:
143 | # pred_labels[np.where(pred_labels == 0)[0]] = sel_clust_id
144 | # pred_labels[np.where(pred_labels == 1)[0]] = i
145 | #else:
146 | # pred_labels[np.where(pred_labels == 1)[0]] = i
147 | # pred_labels[np.where(pred_labels == 0)[0]] = sel_clust_id
148 |
149 | membs[sel_memb_ids] = pred_labels
150 |
151 |
152 | for n in tree.leaves():
153 | label = n.data['label']
154 | centers[label] = n.data['center']
155 | sse_arr[label] = n.data['sse']
156 |
157 | return(centers, membs, sse_arr, tree)
158 |
159 |
160 |
161 |
162 | class BisectKMeans(object):
163 | """
164 | bisecting KMeans Clustering
165 |
166 | Parameters
167 | -------
168 | n_clusters: number of clusters (default = 2)
169 | n_trials: number of trial random centroid initialization (default = 10)
170 | max_iter: maximum number of iterations (default = 100)
171 | tol: tolerance (default = 0.0001)
172 |
173 |
174 | Attibutes
175 | -------
176 | labels_ : cluster labels for each data item
177 | centers_ : cluster centers
178 | sse_arr_ : array of SSE values for each cluster
179 | n_iter_ : number of iterations for the best trial
180 | tree_ : tree hierarchy of bisecting clusters
181 |
182 | Methods
183 | -------
184 | fit(X): fit the model
185 | fit_predict(X): fit the model and return the cluster labels
186 | """
187 |
188 | def __init__(self, n_clusters=2, n_trials=10, max_iter=100, tol=0.0001):
189 | assert n_clusters >= 2, 'n_clusters should be >= 2'
190 | self.n_clusters = n_clusters
191 | self.n_trials = n_trials
192 | self.max_iter = max_iter
193 | self.tol = tol
194 |
195 |
196 | def fit(self, X):
197 | """
198 | """
199 | self.centers_, self.labels_, self.sse_arr_, self.tree_ = \
200 | _bisect_kmeans(X, self.n_clusters, self.n_trials, self.max_iter, self.tol)
201 |
202 |
203 | def fit_predict(self, X):
204 | """
205 | """
206 | self.fit(X)
207 | return(self.labels_)
208 |
209 |
210 | def cut(self, n_desired):
211 | """
212 | """
213 | return(_cut_tree(self.tree_, n_desired, self.labels_))
214 |
--------------------------------------------------------------------------------
/pyclust/_gaussian_mixture_model.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy, scipy.linalg
3 | from . import _kmeans
4 |
5 | Epsilon = 100 * np.finfo(float).eps
6 | Lambda = 0.1
7 |
8 | def _init_mixture_params(X, n_mixtures, init_method):
9 | """
10 | Initialize mixture density parameters with
11 | equal priors
12 | random means
13 | identity covariance matrices
14 | """
15 |
16 | init_priors = np.ones(shape=n_mixtures, dtype=float) / n_mixtures
17 |
18 | if init_method == 'kmeans':
19 | km = _kmeans.KMeans(n_clusters = n_mixtures, n_trials=20)
20 | km.fit(X)
21 | init_means = km.centers_
22 | else:
23 | inx_rand = np.random.choice(X.shape[0], size=n_mixtures)
24 | init_means = X[inx_rand,:]
25 |
26 |
27 | if np.any(np.isnan(init_means)):
28 | raise ValueError("Init means are NaN! ")
29 |
30 | n_features = X.shape[1]
31 | init_covars = np.empty(shape=(n_mixtures, n_features, n_features), dtype=float)
32 | for i in range(n_mixtures):
33 | init_covars[i] = np.eye(n_features)
34 |
35 | return(init_priors, init_means, init_covars)
36 |
37 |
38 |
39 | def __log_density_single(x, mean, covar):
40 | """ This is just a test function to calculate
41 | the normal density at x given mean and covariance matrix.
42 |
43 | Note: this function is not efficient, so
44 | _log_multivariate_density is recommended for use.
45 | """
46 | n_dim = mean.shape[0]
47 |
48 | dx = x - mean
49 | covar_inv = scipy.linalg.inv(covar)
50 | covar_det = scipy.linalg.det(covar)
51 |
52 | den = np.dot(np.dot(dx.T, covar_inv), dx) + n_dim*np.log(2*np.pi) + np.log(covar_det)
53 |
54 | return(-1/2 * den)
55 |
56 |
57 | def _log_multivariate_density(X, means, covars):
58 | """
59 | Class conditional density:
60 | P(x | mu, Sigma) = 1/((2pi)^d/2 * |Sigma|^1/2) * exp(-1/2 * (x-mu)^T * Sigma^-1 * (x-mu))
61 |
62 | log of class conditional density:
63 | log P(x | mu, Sigma) = -1/2*(d*log(2pi) + log(|Sigma|) + (x-mu)^T * Sigma^-1 * (x-mu))
64 | """
65 | n_samples, n_dim = X.shape
66 | n_components = means.shape[0]
67 |
68 | assert(means.shape[0] == covars.shape[0])
69 |
70 | log_proba = np.empty(shape=(n_samples, n_components), dtype=float)
71 | for i, (mu, cov) in enumerate(zip(means, covars)):
72 | try:
73 | cov_chol = scipy.linalg.cholesky(cov, lower=True)
74 | except scipy.linalg.LinAlgError:
75 | try:
76 | cov_chol = scipy.linalg.cholesky(cov + Lambda*np.eye(n_dim), lower=True)
77 | except:
78 | raise ValueError("Triangular Matrix Decomposition not performed!\n")
79 |
80 | cov_log_det = 2 * np.sum(np.log(np.diagonal(cov_chol)))
81 |
82 | try:
83 | cov_solve = scipy.linalg.solve_triangular(cov_chol, (X - mu).T, lower=True).T
84 | except:
85 | raise ValueError("Solve_triangular not perormed!\n")
86 |
87 | log_proba[:, i] = -0.5 * (np.sum(cov_solve ** 2, axis=1) + \
88 | n_dim * np.log(2 * np.pi) + cov_log_det)
89 |
90 | return(log_proba)
91 |
92 |
93 |
94 |
95 | def _log_likelihood_per_sample(X, means, covars):
96 | """
97 | Theta = (theta_1, theta_2, ... theta_M)
98 | Likelihood of mixture parameters given data: L(Theta | X) = product_i P(x_i | Theta)
99 | log likelihood: log L(Theta | X) = sum_i log(P(x_i | Theta))
100 |
101 | and note that p(x_i | Theta) = sum_j prior_j * p(x_i | theta_j)
102 |
103 |
104 | Probability of sample x being generated from component i:
105 | P(w_i | x) = P(x|w_i) * P(w_i) / P(X)
106 | where P(X) = sum_i P(x|w_i) * P(w_i)
107 |
108 | Here post_proba = P/(w_i | x)
109 | and log_likelihood = log(P(x|w_i))
110 | """
111 |
112 | logden = _log_multivariate_density(X, means, covars)
113 |
114 | logden_max = logden.max(axis=1)
115 | log_likelihood = np.log(np.sum(np.exp(logden.T - logden_max) + Epsilon, axis=0))
116 | log_likelihood += logden_max
117 |
118 | post_proba = np.exp(logden - log_likelihood[:, np.newaxis])
119 |
120 | return (log_likelihood, post_proba)
121 |
122 |
123 |
124 | def _validate_params(priors, means, covars):
125 | """ Validation Check for M.L. paramateres
126 | """
127 |
128 | for i,(p,m,cv) in enumerate(zip(priors, means, covars)):
129 | if np.any(np.isinf(p)) or np.any(np.isnan(p)):
130 | raise ValueError("Component %d of priors is not valid " % i)
131 |
132 | if np.any(np.isinf(m)) or np.any(np.isnan(m)):
133 | raise ValueError("Component %d of means is not valid " % i)
134 |
135 | if np.any(np.isinf(cv)) or np.any(np.isnan(cv)):
136 | raise ValueError("Component %d of covars is not valid " % i)
137 |
138 | if (not np.allclose(cv, cv.T) or np.any(scipy.linalg.eigvalsh(cv) <= 0)):
139 | raise ValueError("Component %d of covars must be positive-definite" % i)
140 |
141 |
142 |
143 | def _maximization_step(X, posteriors):
144 | """
145 | Update class parameters as below:
146 | priors: P(w_i) = sum_x P(w_i | x) ==> Then normalize to get in [0,1]
147 | Class means: center_w_i = sum_x P(w_i|x)*x / sum_i sum_x P(w_i|x)
148 | """
149 |
150 | ### Prior probabilities or class weights
151 | sum_post_proba = np.sum(posteriors, axis=0)
152 | prior_proba = sum_post_proba / (sum_post_proba.sum() + Epsilon)
153 |
154 | ### means
155 | means = np.dot(posteriors.T, X) / (sum_post_proba[:, np.newaxis] + Epsilon)
156 |
157 | ### covariance matrices
158 | n_components = posteriors.shape[1]
159 | n_features = X.shape[1]
160 | covars = np.empty(shape=(n_components, n_features, n_features), dtype=float)
161 |
162 | for i in range(n_components):
163 | post_i = posteriors[:, i]
164 | mean_i = means[i]
165 | diff_i = X - mean_i
166 |
167 | with np.errstate(under='ignore'):
168 | covar_i = np.dot(post_i * diff_i.T, diff_i) / (post_i.sum() + Epsilon)
169 | covars[i] = covar_i + Lambda * np.eye(n_features)
170 |
171 |
172 | _validate_params(prior_proba, means, covars)
173 | return(prior_proba, means, covars)
174 |
175 |
176 |
177 | def _fit_gmm_params(X, n_mixtures, n_init, init_method, n_iter, tol):
178 | """
179 | """
180 |
181 | best_mean_loglikelihood = -np.infty
182 |
183 | for _ in range(n_init):
184 | priors, means, covars = _init_mixture_params(X, n_mixtures, init_method)
185 | prev_mean_loglikelihood = None
186 | for i in range(n_iter):
187 | ## E-step
188 | log_likelihoods, posteriors = _log_likelihood_per_sample(X, means, covars)
189 |
190 | ## M-step
191 | priors, means, covars = _maximization_step(X, posteriors)
192 |
193 | ## convergence Check
194 | curr_mean_loglikelihood = log_likelihoods.mean()
195 |
196 | if prev_mean_loglikelihood is not None:
197 | if np.abs(curr_mean_loglikelihood - prev_mean_loglikelihood) < tol:
198 | break
199 |
200 | prev_mean_loglikelihood = curr_mean_loglikelihood
201 |
202 | if curr_mean_loglikelihood > best_mean_loglikelihood:
203 | best_mean_loglikelihood = curr_mean_loglikelihood
204 | best_params = {
205 | 'priors' : priors,
206 | 'means' : means,
207 | 'covars' : covars,
208 | 'mean_log_likelihood' : curr_mean_loglikelihood,
209 | 'n_iter' : i
210 | }
211 |
212 | return(best_params)
213 |
214 |
215 |
216 |
217 | class GMM(object):
218 | """
219 | Gaussian Mixture Model (GMM)
220 |
221 | Parameters
222 | -------
223 |
224 |
225 | Attibutes
226 | -------
227 | labels_ : cluster labels for each data item
228 |
229 |
230 | Methods
231 | -------
232 | fit(X): fit the model
233 | fit_predict(X): fit the model and return the cluster labels
234 | """
235 |
236 | def __init__(self, n_clusters=2, n_trials=10, init_method='', max_iter=100, tol=0.0001):
237 | assert n_clusters >= 2, 'n_clusters should be >= 2'
238 | self.n_clusters = n_clusters
239 | self.n_trials = n_trials
240 | self.init_method = init_method
241 | self.max_iter = max_iter
242 | self.tol = tol
243 |
244 | self.converged = False
245 |
246 |
247 | def fit(self, X):
248 | """ Fit mixture-density parameters with EM algorithm
249 | """
250 | params_dict = _fit_gmm_params(X=X, n_mixtures=self.n_clusters, \
251 | n_init=self.n_trials, init_method=self.init_method, \
252 | n_iter=self.max_iter, tol=self.tol)
253 | self.priors_ = params_dict['priors']
254 | self.means_ = params_dict['means']
255 | self.covars_ = params_dict['covars']
256 |
257 | self.converged = True
258 | self.labels_ = self.predict(X)
259 |
260 |
261 | def predict_proba(self, X):
262 | """
263 | """
264 | if not self.converged:
265 | raise Exception('Mixture model is not fit yet!! Try GMM.fit(X)')
266 | _, post_proba = _log_likelihood_per_sample(X=X, means=self.means_, covars=self.covars_)
267 |
268 | return(post_proba)
269 |
270 |
271 | def predict(self, X):
272 | """
273 | """
274 | post_proba = self.predict_proba(X)
275 |
276 | return(post_proba.argmax(axis=1))
277 |
--------------------------------------------------------------------------------
/pyclust/_kernel_kmeans.py:
--------------------------------------------------------------------------------
1 | ### Theoretical & Algorithmic Discussion
2 | ### with Emad Zahedi
3 | import numpy as np
4 | import scipy, scipy.spatial
5 |
6 |
7 |
8 | def _compute_gram_matrix(X, kernel_type, params):
9 | """
10 | """
11 | if kernel_type == 'rbf':
12 | if 'gamma' in params:
13 | gamma = params['gamma']
14 | else:
15 | gamma = 1.0 / X.shape[1]
16 |
17 | pairwise_dist = scipy.spatial.distance.pdist(X, metric='sqeuclidean')
18 | pairwise_dist = scipy.spatial.distance.squareform(pairwise_dist)
19 | gram_matrix = np.exp( - gamma * pairwise_dist )
20 |
21 | np.fill_diagonal(gram_matrix, 1)
22 | else:
23 | pass
24 |
25 | return(gram_matrix)
26 |
27 |
28 |
29 | def _kernelized_dist2centers(K, n_clusters, wmemb, kernel_dist):
30 | """ Computin the distance in transformed feature space to
31 | cluster centers.
32 |
33 | K is the kernel gram matrix.
34 | wmemb contains cluster assignment. {0,1}
35 |
36 | Assume j is the cluster id:
37 | ||phi(x_i) - Phi_center_j|| = K_ii - 2 sum w_jh K_ih +
38 | sum_r sum_s w_jr w_js K_rs
39 | """
40 | n_samples = K.shape[0]
41 |
42 | for j in range(n_clusters):
43 | memb_j = np.where(wmemb == j)[0]
44 | size_j = memb_j.shape[0]
45 |
46 | K_sub_j = K[memb_j][:, memb_j]
47 |
48 | kernel_dist[:,j] = 1 + np.sum(K_sub_j) /(size_j*size_j)
49 | kernel_dist[:,j] -= 2 * np.sum(K[:, memb_j], axis=1) / size_j
50 |
51 | return
52 |
53 |
54 | def _fit_kernelkmeans(K, n_clusters, n_trials, max_iter, converge_tol=0.001):
55 | """
56 | """
57 | n_samples = K.shape[0]
58 | kdist = np.empty(shape=(n_samples, n_clusters), dtype=float)
59 | within_distances = np.empty(shape=n_clusters, dtype=float)
60 |
61 | best_within_distances = np.infty
62 | for i in range(n_trials):
63 | membs_prev = np.random.randint(n_clusters, size=n_samples)
64 |
65 | for it in range(max_iter):
66 | kdist.fill(0)
67 | _kernelized_dist2centers(K, n_clusters, membs_prev, kdist)
68 |
69 | membs_curr = np.argmin(kdist, axis=1)
70 | membs_changed_ratio = float(np.sum((membs_curr - membs_prev) != 0)) / n_samples
71 |
72 | if membs_changed_ratio < converge_tol:
73 | break
74 |
75 | membs_prev = membs_curr
76 |
77 | for j in range(n_clusters):
78 | within_distances[j] = np.sum(kdist[np.where(membs_curr == j)[0], j])
79 | if best_within_distances > within_distances.sum():
80 | best_within_distances = within_distances.sum()
81 | best_labels = membs_curr
82 |
83 | return(it, best_labels)
84 |
85 |
86 |
87 | class KernelKMeans(object):
88 | """
89 | """
90 |
91 | def __init__(self, n_clusters=2, kernel='linear', params={}, n_trials=10, max_iter=100):
92 | assert (kernel in ['linear', 'rbf'])
93 | self.n_clusters = n_clusters
94 | self.kernel_type = kernel
95 | self.n_trials = n_trials
96 | self.max_iter = max_iter
97 |
98 | self.kernel_params = params
99 | self.kernel_matrix_ = None
100 |
101 |
102 | def _set_kernel_matrix(self, X=None, kernel_matrix=None):
103 | """
104 | """
105 | if self.kernel_matrix_ is None:
106 | if kernel_matrix is None:
107 | if X is None:
108 | raise("Either X or kernel_matrix is needed!")
109 | self.kernel_matrix_ = _compute_gram_matrix(X, self.kernel_type, self.kernel_params)
110 | else:
111 | self.kernel_matrix_ = kernel_matrix
112 |
113 |
114 | def fit(self, X, kernel_matrix=None):
115 | """
116 | """
117 | self._set_kernel_matrix(X, kernel_matrix)
118 | self.n_iter_, self.labels_ = _fit_kernelkmeans(self.kernel_matrix_, self.n_clusters, self.n_trials, self.max_iter)
119 |
120 |
121 | def fit_predict(self, X):
122 | """
123 | """
124 | self.fit(X)
125 | return(self.labels_)
126 |
127 |
128 | ############### Global Kernel K-Means ####################
129 |
130 |
131 | def _fit_global_kernelkmeans(K, n_clusters, max_iter, converge_tol=0.001):
132 | """
133 | """
134 | n_samples = K.shape[0]
135 | kdist = np.empty(shape=(n_samples, n_clusters), dtype=float)
136 | within_distances = np.empty(shape=n_clusters, dtype=float)
137 |
138 | best_within_distances = np.infty
139 | for i in range(n_samples):
140 | membs_prev = np.random.randint(n_clusters, size=n_samples)
141 |
142 | for it in range(max_iter):
143 | kdist.fill(0)
144 | _kernelized_dist2centers(K, n_clusters, membs_prev, kdist)
145 |
146 | membs_curr = np.argmin(kdist, axis=1)
147 | membs_changed_ratio = float(np.sum((membs_curr - membs_prev) != 0)) / n_samples
148 |
149 | if membs_changed_ratio < converge_tol:
150 | break
151 |
152 | membs_prev = membs_curr
153 |
154 | for j in range(n_clusters):
155 | within_distances[j] = np.sum(kdist[np.where(membs_curr == j)[0], j])
156 | if best_within_distances > within_distances.sum():
157 | best_within_distances = within_distances.sum()
158 | best_labels = membs_curr
159 |
160 | return(it, best_labels)
161 |
162 | class GlobalKernelKMeans(object):
163 | """
164 | """
165 | def __init__(self, n_clusters=3, kernel='linear', params={}, n_trials=10, max_iter=100):
166 | self.n_clusters = n_clusters
167 | self.kernel_type = kernel
168 | self.n_trials = n_trials
169 | self.max_iter = max_iter
170 |
171 | self.kernel_params = params
172 | self.kernel_matrix_ = None
173 |
174 | def fit(self, X):
175 | """
176 | """
177 | if self.kernel_matrix_ is None:
178 | self.kernel_matrix_ = _compute_gram_matrix(X, self.kernel_type, self.kernel_params)
179 |
180 |
181 | def refit(self, n_clusters):
182 | """ Extend clustering to a larger number of clusters
183 | """
184 | if n_clusters <= self.n_clusters:
185 | pass
186 | else:
187 | pass
188 |
189 |
--------------------------------------------------------------------------------
/pyclust/_kmeans.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.spatial
3 |
4 | def _kmeans_init(X, n_clusters, method='balanced', rng=None):
5 | """ Initialize k=n_clusters centroids randomly
6 | """
7 | n_samples = X.shape[0]
8 | if rng is None:
9 | cent_idx = np.random.choice(n_samples, replace=False, size=n_clusters)
10 | else:
11 | #print('Generate random centers using RNG')
12 | cent_idx = rng.choice(n_samples, replace=False, size=n_clusters)
13 |
14 | centers = X[cent_idx,:]
15 | mean_X = np.mean(X, axis=0)
16 |
17 | if method == 'balanced':
18 | centers[n_clusters-1] = n_clusters*mean_X - np.sum(centers[:(n_clusters-1)], axis=0)
19 |
20 | return (centers)
21 |
22 |
23 | def _assign_clusters(X, centers):
24 | """ Assignment Step:
25 | assign each point to the closet cluster center
26 | """
27 | dist2cents = scipy.spatial.distance.cdist(X, centers, metric='seuclidean')
28 | membs = np.argmin(dist2cents, axis=1)
29 |
30 | return(membs)
31 |
32 | def _cal_dist2center(X, center):
33 | """ Calculate the SSE to the cluster center
34 | """
35 | dmemb2cen = scipy.spatial.distance.cdist(X, center.reshape(1,X.shape[1]), metric='seuclidean')
36 | return(np.sum(dmemb2cen))
37 |
38 | def _update_centers(X, membs, n_clusters):
39 | """ Update Cluster Centers:
40 | calculate the mean of feature vectors for each cluster
41 | """
42 | centers = np.empty(shape=(n_clusters, X.shape[1]), dtype=float)
43 | sse = np.empty(shape=n_clusters, dtype=float)
44 | for clust_id in range(n_clusters):
45 | memb_ids = np.where(membs == clust_id)[0]
46 |
47 | if memb_ids.shape[0] == 0:
48 | memb_ids = np.random.choice(X.shape[0], size=1)
49 | #print("Empty cluster replaced with ", memb_ids)
50 | centers[clust_id,:] = np.mean(X[memb_ids,:], axis=0)
51 |
52 | sse[clust_id] = _cal_dist2center(X[memb_ids,:], centers[clust_id,:])
53 | return(centers, sse)
54 |
55 |
56 |
57 | def _kmeans_run(X, n_clusters, max_iter, tol):
58 | """ Run a single trial of k-means clustering
59 | on dataset X, and given number of clusters
60 | """
61 | membs = np.empty(shape=X.shape[0], dtype=int)
62 | centers = _kmeans_init(X, n_clusters)
63 |
64 | sse_last = 9999.9
65 | n_iter = 0
66 | for it in range(1,max_iter):
67 | membs = _assign_clusters(X, centers)
68 | centers,sse_arr = _update_centers(X, membs, n_clusters)
69 | sse_total = np.sum(sse_arr)
70 | if np.abs(sse_total - sse_last) < tol:
71 | n_iter = it
72 | break
73 | sse_last = sse_total
74 |
75 | return(centers, membs, sse_total, sse_arr, n_iter)
76 |
77 |
78 | def _kmeans(X, n_clusters, max_iter, n_trials, tol):
79 | """ Run multiple trials of k-means clustering,
80 | and outputt he best centers, and cluster labels
81 | """
82 | n_samples, n_features = X.shape[0], X.shape[1]
83 |
84 | centers_best = np.empty(shape=(n_clusters,n_features), dtype=float)
85 | labels_best = np.empty(shape=n_samples, dtype=int)
86 | for i in range(n_trials):
87 | centers, labels, sse_tot, sse_arr, n_iter = _kmeans_run(X, n_clusters, max_iter, tol)
88 | if i==0:
89 | sse_tot_best = sse_tot
90 | sse_arr_best = sse_arr
91 | n_iter_best = n_iter
92 | centers_best = centers.copy()
93 | labels_best = labels.copy()
94 | if sse_tot < sse_tot_best:
95 | sse_tot_best = sse_tot
96 | sse_arr_best = sse_arr
97 | n_iter_best = n_iter
98 | centers_best = centers.copy()
99 | labels_best = labels.copy()
100 |
101 | return(centers_best, labels_best, sse_arr_best, n_iter_best)
102 |
103 |
104 | class KMeans(object):
105 | """
106 | KMeans Clustering
107 |
108 | Parameters
109 | -------
110 | n_clusters: number of clusters (default = 2)
111 | n_trials: number of trial random centroid initialization (default = 10)
112 | max_iter: maximum number of iterations (default = 100)
113 | tol: tolerance (default = 0.0001)
114 |
115 |
116 | Attibutes
117 | -------
118 | labels_ : cluster labels for each data item
119 | centers_ : cluster centers
120 | sse_arr_ : array of SSE values for each cluster
121 | n_iter_ : number of iterations for the best trial
122 |
123 |
124 | Methods
125 | -------
126 | fit(X): fit the model
127 | fit_predict(X): fit the model and return the cluster labels
128 | """
129 |
130 | def __init__(self, n_clusters=2, n_trials=10, max_iter=100, tol=0.001):
131 |
132 | self.n_clusters = n_clusters
133 | self.n_trials = n_trials
134 | self.max_iter = max_iter
135 | self.tol = tol
136 |
137 | def fit(self, X):
138 | """ Apply KMeans Clustering
139 | X: dataset with feature vectors
140 | """
141 | self.centers_, self.labels_, self.sse_arr_, self.n_iter_ = \
142 | _kmeans(X, self.n_clusters, self.max_iter, self.n_trials, self.tol)
143 |
144 |
145 | def fit_predict(self, X):
146 | """ Apply KMeans Clustering,
147 | and return cluster labels
148 | """
149 | self.fit(X)
150 | return(self.labels_)
151 |
--------------------------------------------------------------------------------
/pyclust/_kmedoids.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.spatial
3 |
4 | from . import _kmeans as kmeans
5 |
6 |
7 |
8 | def _update_centers(X, membs, n_clusters, distance):
9 | """ Update Cluster Centers:
10 | calculate the mean of feature vectors for each cluster.
11 |
12 | distance can be a string or callable.
13 | """
14 | centers = np.empty(shape=(n_clusters, X.shape[1]), dtype=float)
15 | sse = np.empty(shape=n_clusters, dtype=float)
16 | for clust_id in range(n_clusters):
17 | memb_ids = np.where(membs == clust_id)[0]
18 | X_clust = X[memb_ids,:]
19 |
20 | dist = np.empty(shape=memb_ids.shape[0], dtype=float)
21 | for i,x in enumerate(X_clust):
22 | dist[i] = np.sum(scipy.spatial.distance.cdist(X_clust, np.array([x]), distance))
23 |
24 | inx_min = np.argmin(dist)
25 | centers[clust_id,:] = X_clust[inx_min,:]
26 | sse[clust_id] = dist[inx_min]
27 | return(centers, sse)
28 |
29 |
30 |
31 | def _kmedoids_run(X, n_clusters, distance, max_iter, tol, rng):
32 | """ Run a single trial of k-medoids clustering
33 | on dataset X, and given number of clusters
34 | """
35 | membs = np.empty(shape=X.shape[0], dtype=int)
36 | centers = kmeans._kmeans_init(X, n_clusters, method='', rng=rng)
37 |
38 | sse_last = 9999.9
39 | n_iter = 0
40 | for it in range(1,max_iter):
41 | membs = kmeans._assign_clusters(X, centers)
42 | centers,sse_arr = _update_centers(X, membs, n_clusters, distance)
43 | sse_total = np.sum(sse_arr)
44 | if np.abs(sse_total - sse_last) < tol:
45 | n_iter = it
46 | break
47 | sse_last = sse_total
48 |
49 | return(centers, membs, sse_total, sse_arr, n_iter)
50 |
51 |
52 | def _kmedoids(X, n_clusters, distance, max_iter, n_trials, tol, rng):
53 | """ Run multiple trials of k-medoids clustering,
54 | and output he best centers, and cluster labels
55 | """
56 | n_samples, n_features = X.shape[0], X.shape[1]
57 |
58 | centers_best = np.empty(shape=(n_clusters,n_features), dtype=float)
59 | labels_best = np.empty(shape=n_samples, dtype=int)
60 | for i in range(n_trials):
61 | centers, labels, sse_tot, sse_arr, n_iter = \
62 | _kmedoids_run(X, n_clusters, distance, max_iter, tol, rng)
63 | if i==0:
64 | sse_tot_best = sse_tot
65 | sse_arr_best = sse_arr
66 | n_iter_best = n_iter
67 | centers_best = centers.copy()
68 | labels_best = labels.copy()
69 | if sse_tot < sse_tot_best:
70 | sse_tot_best = sse_tot
71 | sse_arr_best = sse_arr
72 | n_iter_best = n_iter
73 | centers_best = centers.copy()
74 | labels_best = labels.copy()
75 |
76 | return(centers_best, labels_best, sse_arr_best, n_iter_best)
77 |
78 |
79 | class KMedoids(object):
80 | """
81 | KMedoids Clustering
82 |
83 | K-medoids clustering take the cluster centroid as the medoid of the data points,
84 | as opposed to the average of data points in a cluster. As a result, K-medoids
85 | gaurantees that the cluster centroid is among the cluster members.
86 |
87 | The medoid is defined as the point that minimizes the total within-cluster distances.
88 |
89 | K-medoids is more robust to outliers (the reason for this is similar to why
90 | median is more robust to mean).
91 |
92 | K-medoids is computationally more expensive, since it involves computation of all
93 | the pairwise distances in a cluster.
94 |
95 |
96 | Parameters
97 | -------
98 | n_clusters: number of clusters (default = 2)
99 | n_trials: number of trial random centroid initialization (default = 10)
100 | max_iter: maximum number of iterations (default = 100)
101 | tol: tolerance (default = 0.0001)
102 |
103 |
104 | Attibutes
105 | -------
106 | labels_ : cluster labels for each data item
107 | centers_ : cluster centers
108 | sse_arr_ : array of SSE values for each cluster
109 | n_iter_ : number of iterations for the best trial
110 |
111 |
112 | Methods
113 | -------
114 | fit(X): fit the model
115 | fit_predict(X): fit the model and return the cluster labels
116 | """
117 |
118 | def __init__(self, n_clusters=2, distance='euclidean',
119 | n_trials=10, max_iter=100, tol=0.001, random_state=None):
120 |
121 | self.n_clusters = n_clusters
122 | self.n_trials = n_trials
123 | self.max_iter = max_iter
124 | self.tol = tol
125 | self.distance = distance
126 | self.random_state = random_state
127 | self.rng = np.random.RandomState(random_state)
128 |
129 | def fit(self, X):
130 | """ Apply KMeans Clustering
131 | X: dataset with feature vectors
132 | """
133 | self.centers_, self.labels_, self.sse_arr_, self.n_iter_ = \
134 | _kmedoids(X, self.n_clusters, self.distance, self.max_iter, self.n_trials, self.tol, self.rng)
135 |
136 |
137 | def fit_predict(self, X):
138 | """ Apply KMeans Clustering,
139 | and return cluster labels
140 | """
141 | self.fit(X)
142 | return(self.labels_)
143 |
--------------------------------------------------------------------------------
/pyclust/validate/__init__.py:
--------------------------------------------------------------------------------
1 | # Vahid Mirjalili 08/07/2015
2 | # Unsupervised Data Clustering Algorithms in Python
3 | # Machine Learning Application
4 |
5 | from ._internal import Silhouette
6 |
7 | __version__ = '0.1.6'
8 |
--------------------------------------------------------------------------------
/pyclust/validate/_internal.py:
--------------------------------------------------------------------------------
1 | import scipy, scipy.spatial
2 | import numpy as np
3 |
4 | import sys
5 |
6 | def _cal_silhouette_score(X, y, sample_size, metric):
7 | """
8 | """
9 | n_samples = X.shape[0]
10 | if sample_size is not None:
11 | assert(1 < sample_size < n_samples)
12 | rand_inx = np.random.choice(X.shape[0], size=sample_size)
13 | X_samp, y_samp = X[rand_inx], y[rand_inx]
14 |
15 | else:
16 | X_samp, y_samp = X, y
17 |
18 | n_clust_samp = len(np.unique(y_samp))
19 | pair_dist = scipy.spatial.distance.pdist(X_samp, metric=metric)
20 |
21 | pair_dist_matrix = scipy.spatial.distance.squareform(pair_dist)
22 |
23 | ## Compute average intra-cluster distances
24 | ## for each of the selected samples
25 | a_arr = _intra_cluster_distances(pair_dist_matrix, y_samp, np.arange(y_samp.shape[0]))
26 |
27 | ## Compute Avg. Distabces to the Neighboring Clusters
28 | b_arr = _neighboring_cluster_distances(pair_dist_matrix, y_samp, np.arange(y_samp.shape[0]))
29 |
30 | comb_arr = np.vstack((a_arr, b_arr))
31 | return((b_arr-a_arr)/np.max(comb_arr, axis=0), a_arr, b_arr)
32 |
33 |
34 | def _intra_cluster_distances(dist_matrix, y, ilist):
35 | """
36 | """
37 | n_samples = y.shape[0]
38 | if type(ilist) == type(list()):
39 | n_inx = len(ilist)
40 | elif type(ilist) == type(np.array([])):
41 | n_inx = ilist.shape[0]
42 | else:
43 | raise Exception("ilist must be iterable!")
44 |
45 | mean_intra_distances = np.empty(shape=n_inx, dtype=float)
46 | for i,inx in enumerate(ilist):
47 | mask = y == y[inx]
48 | mask[inx] = False
49 | if np.sum(mask)>0:
50 | mean_intra_distances[i] = np.mean(dist_matrix[inx][mask]) #/(np.sum(mask) - 1.0)
51 | else:
52 | mean_intra_distances[i] = 0.0
53 | #sys.stderr.write("mask size: %d AvgIntraDist %f\n"%(np.sum(mask), mean_intra_distances[i]))
54 | return(mean_intra_distances)
55 |
56 |
57 |
58 | def _neighboring_cluster_distances(dist_matrix, y, ilist):
59 | """
60 | """
61 | n_samples = y.shape[0]
62 | if type(ilist) == type(list()):
63 | n_inx = len(ilist)
64 | elif type(ilist) == type(np.array([])):
65 | n_inx = ilist.shape[0]
66 | else:
67 | raise Exception("ilist must be iterable!")
68 |
69 | min_clust_distances = np.empty(shape=n_inx, dtype=float)
70 | for i,inx in enumerate(ilist):
71 | y_inx = y[inx]
72 | dist_row_inx = dist_matrix[inx]
73 | dist_2_clusters = [np.mean(dist_row_inx[y == j]) for j in set(y) if not j == y_inx]
74 | min_clust_distances[i] = np.min(dist_2_clusters)
75 |
76 | #sys.stderr.write("mask size: %d Nearest Cluster %f\n"%(np.sum(mask),min_clust_distances[i]))
77 | return(min_clust_distances)
78 |
79 |
80 | class Silhouette(object):
81 | """
82 | """
83 | def __init__(self):
84 | self.n_labels_ = None
85 |
86 | def score(self, X, labels, sample_size=None, metric='euclidean'):
87 | self.sample_scores, self.a_, self.b_ = _cal_silhouette_score(X, labels, sample_size, metric)
88 | self.score = np.mean(self.sample_scores)
89 |
90 | return(self.score, self.a_, self.b_)
91 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy >= 1.9.2
2 | scipy >= 0.15.1
3 | treelib >= 1.3.1
4 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from distutils.core import setup
2 |
3 |
4 | setup(name = "pyclust",
5 | version = "0.1.18",
6 | description = "Data Clustering Algorithms in Python",
7 | author = "Vahid Mirjalili",
8 | author_email = "vmirjalily@gmail.com",
9 | url = "https://github.com/mirjalil/pyclust",
10 |
11 | packages = ['pyclust', 'pyclust.validate'],
12 |
13 | #install_requires=['numpy>=1.9.2',
14 | # 'scipy>=0.15.1',
15 | # 'treelib>=1.3.1'],
16 |
17 | #package *needs* these files.
18 | package_data = {'pyclust':[]},
19 | classifiers = [
20 | "Programming Language :: Python",
21 | "Programming Language :: Python :: 3",
22 | "Intended Audience :: Information Technology",
23 | "Operating System :: OS Independent",
24 | ],
25 |
26 | scripts = [],
27 | long_description = """
28 |
29 |
30 | Contact
31 | =============
32 |
33 | email: vmirjalily@gmail.com
34 | Twitter: https://twitter.com/vmirly
35 |
36 | URL for this project: https://github.com/mirjalil/pyclust
37 |
38 | """,
39 | #classifiers = [],
40 | license='GPLv3',
41 | platforms='any',
42 | )
43 |
44 |
45 |
--------------------------------------------------------------------------------
/tests/test_bi_kmeans.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pyclust
3 |
4 | import treelib
5 |
6 | d1 = np.random.uniform(low=-2, high=2, size=(20,2))
7 | d2 = np.random.uniform(low=2, high=4, size=(10,2))
8 | d3 = np.random.uniform(low=-4, high=-2, size=(10,2))
9 | da = np.vstack((d1,d2,d3))
10 |
11 |
12 | def test_bisect3():
13 | bkm = pyclust.BisectKMeans(n_clusters=4)
14 |
15 | bkm.fit(da)
16 |
17 | print(bkm.labels_)
18 |
19 | leaf_nodes = bkm.tree_.leaves()
20 | for node in leaf_nodes:
21 | print(node.data)
22 |
23 |
24 | bkm.tree_.show(line_type='ascii')
25 |
26 | print("Orig: ", np.unique(bkm.labels_))
27 | print("Cut2: ", np.unique(bkm.cut(2)[0]))
28 | print("Cut3: ", np.unique(bkm.cut(3)[0]))
29 | print("Cut4: ", np.unique(bkm.cut(4)[0]))
30 |
31 |
32 | def test_bisect10():
33 | nsamp = 20
34 | da = np.random.multivariate_normal(mean=(-2,0), cov=[[0.12,0],[0,0.12]], size=nsamp)
35 | ya = np.ones(shape=nsamp, dtype=int)
36 |
37 | db = np.random.multivariate_normal(mean=(-3,1), cov=[[0.08,0.05],[0.05,0.08]], size=nsamp)
38 | yb = 2*np.ones(shape=nsamp, dtype=int)
39 |
40 | dc = np.random.multivariate_normal(mean=(-3,-1), cov=[[0.08,-0.05],[-0.05,0.08]], size=nsamp)
41 | yc = 3*np.ones(shape=nsamp, dtype=int)
42 |
43 | dd = np.random.multivariate_normal(mean=(-1,1), cov=[[0.08,-0.05],[-0.05,0.08]], size=nsamp)
44 | yd = 4*np.ones(shape=nsamp, dtype=int)
45 |
46 | de = np.random.multivariate_normal(mean=(-1,-1), cov=[[0.08,0.05],[0.05,0.08]], size=nsamp)
47 | ye = 5*np.ones(shape=nsamp, dtype=int)
48 |
49 | df = np.random.multivariate_normal(mean=(3,0.6), cov=[[0.05,0.0],[0.0,0.05]], size=nsamp)
50 | yf = 6*np.ones(shape=nsamp, dtype=int)
51 |
52 | dg = np.random.multivariate_normal(mean=(3,-0.6), cov=[[0.05,0.0],[0.0,0.05]], size=nsamp)
53 | yg = 7*np.ones(shape=nsamp, dtype=int)
54 |
55 | X = np.vstack((da, db, dc, dd, de, df, dg))
56 | y = np.hstack((ya, yb, yc, yd, ye, yf, yg))
57 |
58 |
59 | bkm = pyclust.BisectKMeans(n_clusters=10)
60 |
61 | bkm.fit(X)
62 |
63 | leaf_nodes = bkm.tree_.leaves()
64 | for node in leaf_nodes:
65 | print(node.data)
66 |
67 | bkm.tree_.show(line_type='ascii')
68 |
69 | print("Orig: ", np.unique(bkm.labels_))
70 | print("Cut2: ", np.unique(bkm.cut(2)[0]))
71 | print("Cut3: ", np.unique(bkm.cut(3)[0]))
72 | print("Cut4: ", np.unique(bkm.cut(4)[0]))
73 |
74 | print("Cut5: ", np.unique(bkm.cut(5)[0]))
75 | print("Cut6: ", np.unique(bkm.cut(6)[0]))
76 | print("Cut7: ", np.unique(bkm.cut(7)[0]))
77 |
78 | test_bisect10()
79 |
80 | test_bisect3()
81 |
--------------------------------------------------------------------------------
/tests/test_gmm.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy, scipy.linalg
3 | import sys
4 |
5 | import pyclust
6 |
7 |
8 | s1 = np.array([[0.3, 0.2], [0.7, 0.5]])
9 | s2 = np.array([[0.6, 0.0], [0.0, 1.1]])
10 |
11 | m1 = np.array([0.0, 0.0])
12 | m2 = np.array([2.0, -3.0])
13 |
14 | X1 = np.random.multivariate_normal(mean=m1, cov=s1, size=200)
15 | X2 = np.random.multivariate_normal(mean=m2, cov=s2, size=300)
16 |
17 | X = np.vstack((X1, X2))
18 |
19 | #np.savetxt("/tmp/test", X)
20 |
21 | def test_gmm():
22 | gmm = pyclust.GMM(n_clusters=2)
23 |
24 | gmm.fit(X)
25 | print(gmm.priors_)
26 | print(gmm.means_)
27 | print(gmm.covars_)
28 |
29 |
30 | print(gmm.predict(X))
31 |
32 | test_gmm()
33 |
--------------------------------------------------------------------------------
/tests/test_kernel_kmeans.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pyclust
3 |
4 |
5 | d1 = np.random.uniform(low=0, high=3, size=(20,2))
6 | d2 = np.random.uniform(low=3, high=5, size=(20,2))
7 | d = np.vstack((d1,d2))
8 | from scipy.sparse import issparse
9 |
10 | def test_kernelkmeans():
11 | kg = pyclust._kernel_kmeans._compute_gram_matrix(d, kernel_type='rbf', params={})
12 |
13 | #print(kg[0,:])
14 | #print(kg[19,:])
15 |
16 | w = np.random.randint(2, size=20)
17 | wmemb_old = w
18 |
19 | kdist = np.zeros(shape=(20,2), dtype=float)
20 |
21 | #res = pyclust._kernel_kmeans._fit_kernelkmeans(kg, 2, 100)
22 |
23 | kkm = pyclust.KernelKMeans(n_clusters=2, kernel='rbf')
24 | kkm.fit_predict(d)
25 | print("Converged after %d iterations"%kkm.n_iter_)
26 | print(kkm.labels_)
27 |
28 | test_kernelkmeans()
29 |
--------------------------------------------------------------------------------
/tests/test_kmeans.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pyclust
3 |
4 |
5 | d1 = np.random.uniform(low=0, high=2, size=(10,2))
6 | d2 = np.random.uniform(low=0, high=4, size=(10,2))
7 | d = np.vstack((d1,d2))
8 |
9 | print(d.shape)
10 | cent = pyclust._kmeans._kmeans_init(d, 2)
11 | #d2c = scipy.spatial.distance.cdist(d, cent, metric='euclidean')
12 |
13 | print(cent)
14 |
15 | membs = pyclust._kmeans._assign_clusters(d, cent)
16 |
17 | print(membs)
18 |
19 | cent_upd = pyclust._kmeans._update_centers(d, membs, n_clusters=2)
20 |
21 | print(cent_upd)
22 |
23 | print(pyclust._kmeans._kmeans_run(d, n_clusters=2, max_iter=20, tol=0.0001))
24 |
25 | km = pyclust.KMeans(n_clusters=2)
26 |
27 | km.fit(d)
28 |
29 | print("Centers: ", km.centers_)
30 | print("Labels: ", km.labels_)
31 | print("SSE: ", km.sse_arr_)
32 | print("N_ITER: ", km.n_iter_)
33 |
34 | ## Checking the random-number generator
35 | #rng = np.random.RandomState(1234)
36 | print('\n\n*** Testing RandomState: ***')
37 | for i in range(3):
38 | rng = np.random.RandomState(1234)
39 | print(pyclust._kmeans._kmeans_init(d, 2, rng=rng))
40 | print()
41 |
--------------------------------------------------------------------------------
/tests/test_kmedoids.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pyclust
3 |
4 |
5 | d1 = np.random.uniform(low=0, high=2, size=(10,2))
6 | d2 = np.random.uniform(low=0, high=4, size=(10,2))
7 | d = np.vstack((d1,d2))
8 |
9 | print(d.shape)
10 | cent = pyclust._kmeans._kmeans_init(d, 2)
11 | #d2c = scipy.spatial.distance.cdist(d, cent, metric='euclidean')
12 |
13 | print(cent)
14 |
15 | membs = pyclust._kmeans._assign_clusters(d, cent)
16 |
17 | print(membs)
18 |
19 | cent_upd = pyclust._kmedoids._update_centers(d, membs, n_clusters=2, distance='euclidean')
20 |
21 | print(cent_upd)
22 |
23 |
24 | rng = np.random.RandomState(1234)
25 | print(pyclust._kmedoids._kmedoids_run(d, n_clusters=2, distance='euclidean', max_iter=20, tol=0.001, rng=rng))
26 |
27 | kmd = pyclust.KMedoids(n_clusters=2)
28 |
29 | kmd.fit(d)
30 |
31 | print("Centers: ", kmd.centers_)
32 | print("Labels: ", kmd.labels_)
33 | print("SSE: ", kmd.sse_arr_)
34 | print("N_ITER: ", kmd.n_iter_)
35 |
36 |
37 | print('\n\n*** Testing RandomState: ***')
38 | for i in range(3):
39 | kmd2 = pyclust.KMedoids(n_clusters=2, random_state=123)
40 | kmd2.fit(d)
41 | print("Centers: ", kmd2.centers_)
42 | print("Labels: ", kmd2.labels_)
43 | print("SSE: ", kmd2.sse_arr_)
44 | print("N_ITER: ", kmd2.n_iter_)
45 | print()
46 |
47 |
--------------------------------------------------------------------------------
/tests/test_silhouette.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy, scipy.linalg
3 | import sys
4 |
5 | import pyclust, pyclust.validate
6 | from sklearn.metrics import silhouette_score, silhouette_samples
7 |
8 | s1 = np.array([[0.3, 0.2], [0.7, 0.5]])
9 | s2 = np.array([[0.6, 0.0], [0.0, 1.1]])
10 |
11 | m1 = np.array([0.0, 0.0])
12 | m2 = np.array([2.0, -3.0])
13 |
14 | X1 = np.random.multivariate_normal(mean=m1, cov=s1, size=200)
15 | X2 = np.random.multivariate_normal(mean=m2, cov=s2, size=300)
16 |
17 | X = np.vstack((X1, X2))
18 |
19 | #X = np.array([[-1,0],[0,0],[-1,-1],[2,0],[2,1],[3,0]])
20 | #X = np.array([[-1,0],[0,0],[0,0],[2,0],[2,0],[3,0]])
21 | #ypred = np.array([1,1,1,2,2,2])
22 | ypred = np.hstack((np.zeros(shape=200), np.ones(shape=300)))
23 | print(ypred.shape)
24 |
25 | #np.savetxt("/tmp/test", X)
26 |
27 | def test_gmm():
28 | sil = pyclust.validate.Silhouette()
29 | sil_score = sil.score(X, ypred, sample_size=None)
30 |
31 | print(sil_score[0])
32 |
33 | print(sil.sample_scores[:10])
34 |
35 | print(silhouette_score(X, ypred, sample_size=None))
36 |
37 | print(silhouette_samples(X, ypred)[:10])
38 | test_gmm()
39 |
--------------------------------------------------------------------------------