Skip to content

Commit 7e1b38e

Browse files
authored
fix: accept array of categories in classifier initialization (#111)
* fix: accept array of categories in classifier initialization Bayes.new and LogisticRegression.new now accept either: - Splat arguments: Bayes.new('Spam', 'Ham') - Array argument: Bayes.new(['Spam', 'Ham']) Both forms are now equivalent, fixing the issue where array arguments were treated as a single category name. Fixes #110 * docs: add visual indicators to comparison table and fix URL Add checkmark and X emoji to the Why This Library comparison table for better visual scanning. Fix Logistic Regression guide URL to use correct path (logisticregression without hyphen).
1 parent fe24d99 commit 7e1b38e

5 files changed

Lines changed: 33 additions & 9 deletions

File tree

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ Text classification in Ruby. Five algorithms, native performance, streaming supp
1212

1313
| | This Gem | Other Forks |
1414
|:--|:--|:--|
15-
| **Algorithms** | 5 classifiers | 2 only |
16-
| **Incremental LSI** | Brand's algorithm (no rebuild) | Full SVD rebuild on every add |
17-
| **LSI Performance** | Native C extension (5-50x faster) | Pure Ruby or requires GSL |
18-
| **Streaming** | Train on multi-GB datasets | Must load all data in memory |
19-
| **Persistence** | Pluggable (file, Redis, S3) | Marshal only |
15+
| **Algorithms** | 5 classifiers | 2 only |
16+
| **Incremental LSI** | Brand's algorithm (no rebuild) | Full SVD rebuild on every add |
17+
| **LSI Performance** | Native C extension (5-50x faster) | Pure Ruby or requires GSL |
18+
| **Streaming** | Train on multi-GB datasets | Must load all data in memory |
19+
| **Persistence** | Pluggable (file, Redis, S3) | Marshal only |
2020

2121
## Installation
2222

@@ -42,7 +42,7 @@ classifier = Classifier::LogisticRegression.new(:positive, :negative)
4242
classifier.train(positive: "Great product!", negative: "Terrible experience")
4343
classifier.classify "Loved it!" # => "Positive"
4444
```
45-
[Logistic Regression Guide →](https://rubyclassifier.com/docs/guides/logistic-regression/basics)
45+
[Logistic Regression Guide →](https://rubyclassifier.com/docs/guides/logisticregression/basics)
4646

4747
### LSI (Latent Semantic Indexing)
4848

lib/classifier/bayes.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ class Bayes # rubocop:disable Metrics/ClassLength
2626
# The class can be created with one or more categories, each of which will be
2727
# initialized and given a training method. E.g.,
2828
# b = Classifier::Bayes.new 'Interesting', 'Uninteresting', 'Spam'
29-
# @rbs (*String | Symbol) -> void
29+
# b = Classifier::Bayes.new ['Interesting', 'Uninteresting', 'Spam']
30+
# @rbs (*String | Symbol | Array[String | Symbol]) -> void
3031
def initialize(*categories)
3132
super()
3233
@categories = {}
33-
categories.each { |category| @categories[category.prepare_category_name] = {} }
34+
categories.flatten.each { |category| @categories[category.prepare_category_name] = {} }
3435
@total_words = 0
3536
@category_counts = Hash.new(0)
3637
@category_word_count = Hash.new(0)

lib/classifier/logistic_regression.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,22 @@ class LogisticRegression # rubocop:disable Metrics/ClassLength
4646
#
4747
# classifier = Classifier::LogisticRegression.new(:spam, :ham)
4848
# classifier = Classifier::LogisticRegression.new('Positive', 'Negative', 'Neutral')
49+
# classifier = Classifier::LogisticRegression.new(['Positive', 'Negative', 'Neutral'])
4950
#
5051
# Options:
5152
# - learning_rate: Step size for gradient descent (default: 0.1)
5253
# - regularization: L2 regularization strength (default: 0.01)
5354
# - max_iterations: Maximum training iterations (default: 100)
5455
# - tolerance: Convergence threshold (default: 1e-4)
5556
#
56-
# @rbs (*String | Symbol, ?learning_rate: Float, ?regularization: Float,
57+
# @rbs (*String | Symbol | Array[String | Symbol], ?learning_rate: Float, ?regularization: Float,
5758
# ?max_iterations: Integer, ?tolerance: Float) -> void
5859
def initialize(*categories, learning_rate: DEFAULT_LEARNING_RATE,
5960
regularization: DEFAULT_REGULARIZATION,
6061
max_iterations: DEFAULT_MAX_ITERATIONS,
6162
tolerance: DEFAULT_TOLERANCE)
6263
super()
64+
categories = categories.flatten
6365
raise ArgumentError, 'At least two categories required' if categories.size < 2
6466

6567
@categories = categories.map { |c| c.to_s.prepare_category_name }

test/bayes/bayesian_test.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ def test_categories
1717
assert_equal %w[Interesting Uninteresting].sort, @classifier.categories.sort
1818
end
1919

20+
def test_array_initialization
21+
classifier = Classifier::Bayes.new(%w[Spam Ham])
22+
23+
assert_equal %w[Ham Spam], classifier.categories.sort
24+
25+
classifier.train_spam 'bad nasty spam email'
26+
classifier.train_ham 'good legitimate email'
27+
28+
assert_equal 'Spam', classifier.classify('this is spam')
29+
end
30+
2031
def test_add_category
2132
@classifier.add_category 'Test'
2233

test/logistic_regression/logistic_regression_test.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ def test_accepts_symbols_and_strings
1919
assert_equal %w[Spam Ham].sort, classifier2.categories.sort
2020
end
2121

22+
def test_accepts_array_of_categories
23+
classifier = Classifier::LogisticRegression.new(%w[Spam Ham])
24+
25+
assert_equal %w[Ham Spam], classifier.categories.sort
26+
end
27+
28+
def test_array_initialization_requires_at_least_two
29+
assert_raises(ArgumentError) { Classifier::LogisticRegression.new(['Only']) }
30+
end
31+
2232
def test_custom_hyperparameters
2333
classifier = Classifier::LogisticRegression.new(
2434
:spam, :ham,

0 commit comments

Comments
 (0)