├── .gitignore
├── .styleci.yml
├── README.md
├── changelog.md
├── composer.json
├── config
└── rubixai_config.php
├── contributing.md
├── license.md
├── phpunit.xml
├── src
├── Exceptions
│ └── RubixAiGeneralException.php
├── Facades
│ └── RubixAi.php
├── RubixAiService.php
└── RubixAiServiceProvider.php
└── tests
└── Unit
├── CanTrainAnomalyTest.php
├── CanTrainClassification.php
├── CanTrainClusterer.php
├── CanTrainRegression.php
├── CanTrainRegressionSimple.php
├── LConfigTest.php
└── TestEloquentModels
├── Apartment.php
├── IrisFlower.php
└── csv
├── apartments_1k.csv
└── bezdekiris.csv
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.lock
3 | *.cache
4 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: laravel
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # torian257x/ai-php-rubix-wrap-laravel
2 | This package is about Artificial Intelligence, Machine Learning and Pattern Recognition in Laravel.
3 |
4 | The video tutorial with examples on how to use the library can be found here:
5 |
6 | ## Video tutorial
7 | [how to use AI in Laravel](https://youtu.be/0i2npIenj70) ([full playlist](https://www.youtube.com/watch?v=0i2npIenj70&list=PLInLuJxdnhE-qZJHhhQka0osgYz8_Kn6i))
8 |
9 |
10 |
11 |
12 | ## Example code
13 | Do AI with 2 lines of code:
14 |
15 | ```
16 | $all_animals = DogsAndCats::all();
17 |
18 | $report = RubixAi::train($all_animals, 'dog_or_cat');
19 | ```
20 |
21 | that's it.
22 |
23 | This creates a file in your laravel storage/ai_rubix/ folder that contains the model.
24 |
25 | This model then will be used to predict:
26 |
27 | ```
28 | $isDogOrCat = RubixAi::predict($needs_prediction);
29 |
30 | echo $isDogOrCat; //prints ['dog']
31 | ```
32 |
33 | ## Installation
34 |
35 | ```
36 | composer require torian257x/ai-php-rubix-wrap-laravel
37 | ```
38 |
39 | If there are any issues, please have a look at https://docs.rubixml.com/latest/installation.html in case you are trying to do something special. I recommend as well installing tensor https://github.com/Scien-ide/Tensor .
40 |
41 | ## Default Estimator
42 | `new KDNeighborsRegressor()` for regression (say estimation of price)
43 |
44 | `new KDNeighbors()` for classification (say choose dog or cat)
45 |
46 | ## Default Transformers
47 | ```
48 | array_filter(
49 | [
50 | new NumericStringConverter(),
51 | new MissingDataImputer(),
52 | $needs_ohe ? new OneHotEncoder() : false,
53 | new MinMaxNormalizer(),
54 | ]);
55 | ```
56 |
57 | ## Customize
58 | You can [choose your own estimator](https://docs.rubixml.com/latest/choosing-an-estimator.html) if you don't like the default
59 | Just be sure to pass it as argument to `RubixAi::train(..., estimator_algorithm:<>)`
60 |
61 | Same for transformers.
62 |
63 | You can customize the model file name as well as what attributes / columns to ignore.
64 |
65 |
66 |
67 | ## References
68 | This library is using a [PHP standalone AI wrapper](https://github.com/torian257x/ai-php-rubix-wrap) of [Rubix ML](https://github.com/RubixML/ML)
69 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `RubixAi` will be documented in this file.
4 |
5 | ## Version 1.0
6 |
7 | ### Added
8 | - Everything
9 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "torian257x/ai-php-rubix-wrap-laravel",
3 | "description": "Meant for AI using a wrapped Rubix ML library to make it very approachable",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Lukas Meier",
8 | "email": "torian257x@gmail.com"
9 | }
10 | ],
11 | "homepage": "https://github.com/torian257x/rubixai",
12 | "keywords": ["Laravel", "RubixAi"],
13 | "require": {
14 | "illuminate/support": "~7|~8",
15 | "calebporzio/sushi": "^2.2",
16 | "torian257x/ai-php-rubix-wrap": "^0.9"
17 | },
18 | "require-dev": {
19 | "phpunit/phpunit": "~9.0",
20 | "orchestra/testbench": "~5|~6"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "Torian257x\\RubixAi\\": "src/"
25 | }
26 | },
27 | "autoload-dev": {
28 | "psr-4": {
29 | "Torian257x\\RubixAi\\Tests\\": "tests"
30 | }
31 | },
32 | "extra": {
33 | "laravel": {
34 | "providers": [
35 | "Torian257x\\RubixAi\\RubixAiServiceProvider"
36 | ],
37 | "aliases": {
38 | "RubixAi": "Torian257x\\RubixAi\\Facades\\RubixAi"
39 | }
40 | }
41 | },
42 | "repositories": {
43 | "torian257x/ai-php-rubix-wrap": {
44 | "type": "path",
45 | "url": "/home/jossnaz/IdeaProjects/rubix-wrap",
46 | "options": {
47 | "symlink": true
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/config/rubixai_config.php:
--------------------------------------------------------------------------------
1 | storage_path('ai_rubix/csv/'),
7 | 'csv_path_input' => storage_path('ai_rubix/csv/'),
8 | 'ai_model_path_output' => storage_path('ai_rubix/model/'),
9 | 'RubixMainClass' => RubixService::class,
10 | ];
11 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are welcome and will be fully credited.
4 |
5 | Contributions are accepted via Pull Requests on [Github](https://github.com/torian257x/rubixai).
6 |
7 | # Things you could do
8 | If you want to contribute but do not know where to start, this list provides some starting points.
9 | - Add license text
10 | - Remove rewriteRules.php
11 | - Set up TravisCI, StyleCI, ScrutinizerCI
12 | - Write a comprehensive ReadMe
13 |
14 | ## Pull Requests
15 |
16 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
17 |
18 | - **Document any change in behaviour** - Make sure the `readme.md` and any other relevant documentation are kept up-to-date.
19 |
20 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
21 |
22 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
23 |
24 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
25 |
26 |
27 | **Happy coding**!
28 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | # The license
2 |
3 | Copyright (c) Lukas Meier
4 |
5 | ...Add your license text here...
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | ./tests/Unit
14 |
15 |
16 |
17 |
18 |
19 | src
20 |
21 | src/Examples
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/Exceptions/RubixAiGeneralException.php:
--------------------------------------------------------------------------------
1 | toArray();
92 | } else if ($data instanceof \iterable){
93 | $rv = iterator_to_array($data);
94 | }else{
95 | throw new RubixAiGeneralException("Cannot convert this data type to array: " . get_class($data));
96 | }
97 |
98 | if(!is_null($ignore_attrs)){
99 | foreach($rv as &$v){
100 | foreach($ignore_attrs as $i){
101 | unset($v[$i]);
102 | }
103 | }
104 | }
105 |
106 | return $rv;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/RubixAiServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->runningInConsole()) {
18 | $this->bootForConsole();
19 | }
20 | }
21 |
22 | /**
23 | * Register any package services.
24 | *
25 | * @return void
26 | */
27 | public function register(): void
28 | {
29 | $this->mergeConfigFrom(__DIR__ . '/../config/rubixai_config.php', 'rubixai');
30 | if(!defined('RUBIXAI_CUSTOM_CONFIG')){
31 | define('RUBIXAI_CUSTOM_CONFIG', config('rubixai'));
32 | }
33 |
34 | $this->app->singleton('rubixai', function ($app) {
35 | return new RubixAiService;
36 | });
37 | }
38 |
39 | /**
40 | * Get the services provided by the provider.
41 | *
42 | * @return array
43 | */
44 | public function provides()
45 | {
46 | return ['rubixai'];
47 | }
48 |
49 | /**
50 | * Console-specific booting.
51 | *
52 | * @return void
53 | */
54 | protected function bootForConsole(): void
55 | {
56 | // Publishing the configuration file.
57 | $this->publishes([
58 | __DIR__ . '/../config/rubixai_config.php' => config_path('rubixai.php'),
59 | ], 'rubixai.config');
60 |
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Unit/CanTrainAnomalyTest.php:
--------------------------------------------------------------------------------
1 | get();
28 |
29 | $apartments = $apartments->map(function($a){
30 |
31 | if($a->water_heating === 'No tiene'){
32 | $a->water_heating = 0;
33 | }else if($a->water_heating === 'Gas'){
34 | $a->water_heating = 1;
35 | }else if($a->water_heating === 'Eléctrico'){
36 | $a->water_heating = 0.5;
37 | }else{
38 | $a->water_heating = 0;
39 | }
40 |
41 | if($a->doorman === '24 Horas'){
42 | $a->doorman = 1;
43 | }else if($a->doorman === 'Diurna'){
44 | $a->doorman = 0.5;
45 | }else if($a->doorman === 'No tiene'){
46 | $a->doorman = 0;
47 | }else{
48 | $a->doorman = 0;
49 | }
50 |
51 | return $a;
52 |
53 | });
54 |
55 | $apartments->makeHidden(['zone_2_id', 'zone_id']);
56 |
57 | $data = RubixAi::trainWithoutTest($apartments, estimator_algorithm: new GaussianMLE(contamination: 0.005));
58 |
59 |
60 |
61 | RubixAi::toCsv($data, 'cluster_out.csv');
62 | }
63 |
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Unit/CanTrainClassification.php:
--------------------------------------------------------------------------------
1 | get();
38 | $data = $data->map(
39 | function (Apartment $a) {
40 |
41 | $wh = $a->water_heating;
42 |
43 | if ($wh === 'No tiene') {
44 | $wh_val = 0;
45 | } else if ($wh === 'Gas') {
46 | $wh_val = 1;
47 | } else if ($wh === 'Eléctrico') {
48 | $wh_val = 0.5;
49 | } else {
50 | $wh_val = 0;
51 | }
52 | $a->water_heating = $wh_val;
53 |
54 | $dm = $a->doorman;
55 |
56 |
57 | if ($dm === '24 Horas') {
58 | $dm_val = 1;
59 | } else if ($dm === 'Diurna') {
60 | $dm_val = 0.5;
61 | } else if ($dm === 'No tiene') {
62 | $dm_val = 0;
63 | } else {
64 | $dm_val = 0;
65 | }
66 |
67 | $a->doorman = $dm_val;
68 |
69 | $a->rr = pow($a->rooms, 2);
70 | $a->pp = pow($a->price_millions, 2);
71 | $a->pp3 = pow($a->price_millions, 3);
72 | $a->p_t_r = $a->price_millions * $a->rooms ;
73 | $a->p_t_lat = $a->price_millions * $a->geo_lat;
74 | $a->p_t_lng = $a->price_millions * $a->geo_lng;
75 | $a->latlng1 = $a->geo_lat * $a->geo_lng;
76 | $a->latlng2 = pow($a->geo_lat * $a->geo_lng, 2);
77 |
78 | $rv = $a->toArray();
79 |
80 | unset($rv['zone_id']);
81 | unset($rv['zone_2_id']);
82 |
83 | return $rv;
84 | }
85 | );
86 |
87 |
88 | return $data;
89 | }
90 |
91 | public function testGetSimilar()
92 | {
93 |
94 | $data = self::getData();
95 |
96 | $nr_groups = ceil(sqrt(count($data) / 2));
97 |
98 |
99 | $data_w_cluster_nr = RubixAi::trainWithoutTest(
100 | $data,
101 | estimator_algorithm: new KMeans($nr_groups, kernel: new Manhattan()),
102 | transformers: [
103 | new MissingDataImputer(),
104 | new NumericStringConverter(),
105 | new ZScaleStandardizer(),
106 | ]
107 | );
108 |
109 | $clusters = array_column($data_w_cluster_nr,'cluster_nr');
110 | $sum_clusters = array_sum($clusters);
111 |
112 |
113 | self::assertGreaterThan(100, $sum_clusters);
114 |
115 |
116 | }
117 |
118 | }
119 |
120 |
--------------------------------------------------------------------------------
/tests/Unit/CanTrainRegression.php:
--------------------------------------------------------------------------------
1 | get();
27 |
28 | $report = RubixAi::train($apartments, 'price_millions', train_part_size: 0.95);
29 |
30 | self::assertLessThan(0.4, $report['mean absolute error']);
31 | }
32 |
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Unit/CanTrainRegressionSimple.php:
--------------------------------------------------------------------------------
1 | 10, 'price' => 100],
27 | ['space_m2' => 20, 'price' => 200],
28 | ['space_m2' => 30, 'price' => 300],
29 | ['space_m2' => 40, 'price' => 400],
30 | ['space_m2' => 50, 'price' => 500],
31 | ['space_m2' => 60, 'price' => 600],
32 | ['space_m2' => 70, 'price' => 700],
33 | ['space_m2' => 80, 'price' => 800],
34 | ['space_m2' => 90, 'price' => 900],
35 | ['space_m2' => 100, 'price' => 1000],
36 | ['space_m2' => 110, 'price' => 1100],
37 | ['space_m2' => 120, 'price' => 1200],
38 | ['space_m2' => 130, 'price' => 1300],
39 | ['space_m2' => 140, 'price' => 1400],
40 | ['space_m2' => 150, 'price' => 1500],
41 | ['space_m2' => 160, 'price' => 1600],
42 | ['space_m2' => 170, 'price' => 1700],
43 | ['space_m2' => 180, 'price' => 1800],
44 | ['space_m2' => 190, 'price' => 1900],
45 | ['space_m2' => 200, 'price' => 2000],
46 | ['space_m2' => 210, 'price' => 2100],
47 | ['space_m2' => 220, 'price' => 2200],
48 | ['space_m2' => 230, 'price' => 2300],
49 | ['space_m2' => 240, 'price' => 2400],
50 | ['space_m2' => 250, 'price' => 2500],
51 | ['space_m2' => 260, 'price' => 2600],
52 | ['space_m2' => 270, 'price' => 2700],
53 | ['space_m2' => 280, 'price' => 2800],
54 | ['space_m2' => 290, 'price' => 2900],
55 | ['space_m2' => 300, 'price' => 3000],
56 | ];
57 |
58 |
59 | $report = RubixAi::train($apartment_data, 'price',);
60 |
61 | self::assertGreaterThan(0.9, $report['r squared']);
62 |
63 |
64 | $prediction = RubixAi::predict(['space_m2' => 250]);
65 |
66 | self::assertLessThan(0.1 * 2500, abs($prediction - 2500));
67 | }
68 |
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/tests/Unit/LConfigTest.php:
--------------------------------------------------------------------------------
1 | 'float',
15 | 'price_millions' => 'float',
16 | 'space' => 'int',
17 | 'rooms' => 'int',
18 | 'zone_id' => 'string',
19 | 'zone_2_id' => 'string',
20 | 'geo_lat' => 'float',
21 | 'geo_lng' => 'float',
22 | 'parking' => 'int',
23 | 'water_heating' => 'string',
24 | 'doorman' => 'string',
25 | 'price_level' => 'int',
26 | 'gas_network' => 'string',
27 | 'balcony' => 'int',
28 | 'linen_room' => 'int',
29 | ];
30 |
31 | public function getRows()
32 | {
33 | $data = new CSV(__DIR__ .'/csv/apartments_1k.csv', true);
34 | $rows = iterator_to_array($data);
35 | return $rows;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Unit/TestEloquentModels/IrisFlower.php:
--------------------------------------------------------------------------------
1 | 'float',
15 | 'sepal_width_cm' => 'float',
16 | 'petal_length_cm' => 'float',
17 | 'petal_width_cm' => 'float',
18 | 'iris_plant_type' => 'string',
19 | ];
20 |
21 | public function getRows()
22 | {
23 | $data = new CSV(__DIR__ .'/csv/bezdekiris.csv', true);
24 | $rows = iterator_to_array($data);
25 | return $rows;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Unit/TestEloquentModels/csv/bezdekiris.csv:
--------------------------------------------------------------------------------
1 | sepal_length_cm,sepal_width_cm,petal_length_cm,petal_width_cm,iris_plant_type
2 | 5.1,3.5,1.4,0.2,setosa
3 | 4.9,3,1.4,0.2,setosa
4 | 4.7,3.2,1.3,0.2,setosa
5 | 4.6,3.1,1.5,0.2,setosa
6 | 5,3.6,1.4,0.2,setosa
7 | 5.4,3.9,1.7,0.4,setosa
8 | 4.6,3.4,1.4,0.3,setosa
9 | 5,3.4,1.5,0.2,setosa
10 | 4.4,2.9,1.4,0.2,setosa
11 | 4.9,3.1,1.5,0.1,setosa
12 | 5.4,3.7,1.5,0.2,setosa
13 | 4.8,3.4,1.6,0.2,setosa
14 | 4.8,3,1.4,0.1,setosa
15 | 4.3,3,1.1,0.1,setosa
16 | 5.8,4,1.2,0.2,setosa
17 | 5.7,4.4,1.5,0.4,setosa
18 | 5.4,3.9,1.3,0.4,setosa
19 | 5.1,3.5,1.4,0.3,setosa
20 | 5.7,3.8,1.7,0.3,setosa
21 | 5.1,3.8,1.5,0.3,setosa
22 | 5.4,3.4,1.7,0.2,setosa
23 | 5.1,3.7,1.5,0.4,setosa
24 | 4.6,3.6,1,0.2,setosa
25 | 5.1,3.3,1.7,0.5,setosa
26 | 4.8,3.4,1.9,0.2,setosa
27 | 5,3,1.6,0.2,setosa
28 | 5,3.4,1.6,0.4,setosa
29 | 5.2,3.5,1.5,0.2,setosa
30 | 5.2,3.4,1.4,0.2,setosa
31 | 4.7,3.2,1.6,0.2,setosa
32 | 4.8,3.1,1.6,0.2,setosa
33 | 5.4,3.4,1.5,0.4,setosa
34 | 5.2,4.1,1.5,0.1,setosa
35 | 5.5,4.2,1.4,0.2,setosa
36 | 4.9,3.1,1.5,0.2,setosa
37 | 5,3.2,1.2,0.2,setosa
38 | 5.5,3.5,1.3,0.2,setosa
39 | 4.9,3.6,1.4,0.1,setosa
40 | 4.4,3,1.3,0.2,setosa
41 | 5.1,3.4,1.5,0.2,setosa
42 | 5,3.5,1.3,0.3,setosa
43 | 4.5,2.3,1.3,0.3,setosa
44 | 4.4,3.2,1.3,0.2,setosa
45 | 5,3.5,1.6,0.6,setosa
46 | 5.1,3.8,1.9,0.4,setosa
47 | 4.8,3,1.4,0.3,setosa
48 | 5.1,3.8,1.6,0.2,setosa
49 | 4.6,3.2,1.4,0.2,setosa
50 | 5.3,3.7,1.5,0.2,setosa
51 | 5,3.3,1.4,0.2,setosa
52 | 7,3.2,4.7,1.4,versicolor
53 | 6.4,3.2,4.5,1.5,versicolor
54 | 6.9,3.1,4.9,1.5,versicolor
55 | 5.5,2.3,4,1.3,versicolor
56 | 6.5,2.8,4.6,1.5,versicolor
57 | 5.7,2.8,4.5,1.3,versicolor
58 | 6.3,3.3,4.7,1.6,versicolor
59 | 4.9,2.4,3.3,1,versicolor
60 | 6.6,2.9,4.6,1.3,versicolor
61 | 5.2,2.7,3.9,1.4,versicolor
62 | 5,2,3.5,1,versicolor
63 | 5.9,3,4.2,1.5,versicolor
64 | 6,2.2,4,1,versicolor
65 | 6.1,2.9,4.7,1.4,versicolor
66 | 5.6,2.9,3.6,1.3,versicolor
67 | 6.7,3.1,4.4,1.4,versicolor
68 | 5.6,3,4.5,1.5,versicolor
69 | 5.8,2.7,4.1,1,versicolor
70 | 6.2,2.2,4.5,1.5,versicolor
71 | 5.6,2.5,3.9,1.1,versicolor
72 | 5.9,3.2,4.8,1.8,versicolor
73 | 6.1,2.8,4,1.3,versicolor
74 | 6.3,2.5,4.9,1.5,versicolor
75 | 6.1,2.8,4.7,1.2,versicolor
76 | 6.4,2.9,4.3,1.3,versicolor
77 | 6.6,3,4.4,1.4,versicolor
78 | 6.8,2.8,4.8,1.4,versicolor
79 | 6.7,3,5,1.7,versicolor
80 | 6,2.9,4.5,1.5,versicolor
81 | 5.7,2.6,3.5,1,versicolor
82 | 5.5,2.4,3.8,1.1,versicolor
83 | 5.5,2.4,3.7,1,versicolor
84 | 5.8,2.7,3.9,1.2,versicolor
85 | 6,2.7,5.1,1.6,versicolor
86 | 5.4,3,4.5,1.5,versicolor
87 | 6,3.4,4.5,1.6,versicolor
88 | 6.7,3.1,4.7,1.5,versicolor
89 | 6.3,2.3,4.4,1.3,versicolor
90 | 5.6,3,4.1,1.3,versicolor
91 | 5.5,2.5,4,1.3,versicolor
92 | 5.5,2.6,4.4,1.2,versicolor
93 | 6.1,3,4.6,1.4,versicolor
94 | 5.8,2.6,4,1.2,versicolor
95 | 5,2.3,3.3,1,versicolor
96 | 5.6,2.7,4.2,1.3,versicolor
97 | 5.7,3,4.2,1.2,versicolor
98 | 5.7,2.9,4.2,1.3,versicolor
99 | 6.2,2.9,4.3,1.3,versicolor
100 | 5.1,2.5,3,1.1,versicolor
101 | 5.7,2.8,4.1,1.3,versicolor
102 | 6.3,3.3,6,2.5,virginica
103 | 5.8,2.7,5.1,1.9,virginica
104 | 7.1,3,5.9,2.1,virginica
105 | 6.3,2.9,5.6,1.8,virginica
106 | 6.5,3,5.8,2.2,virginica
107 | 7.6,3,6.6,2.1,virginica
108 | 4.9,2.5,4.5,1.7,virginica
109 | 7.3,2.9,6.3,1.8,virginica
110 | 6.7,2.5,5.8,1.8,virginica
111 | 7.2,3.6,6.1,2.5,virginica
112 | 6.5,3.2,5.1,2,virginica
113 | 6.4,2.7,5.3,1.9,virginica
114 | 6.8,3,5.5,2.1,virginica
115 | 5.7,2.5,5,2,virginica
116 | 5.8,2.8,5.1,2.4,virginica
117 | 6.4,3.2,5.3,2.3,virginica
118 | 6.5,3,5.5,1.8,virginica
119 | 7.7,3.8,6.7,2.2,virginica
120 | 7.7,2.6,6.9,2.3,virginica
121 | 6,2.2,5,1.5,virginica
122 | 6.9,3.2,5.7,2.3,virginica
123 | 5.6,2.8,4.9,2,virginica
124 | 7.7,2.8,6.7,2,virginica
125 | 6.3,2.7,4.9,1.8,virginica
126 | 6.7,3.3,5.7,2.1,virginica
127 | 7.2,3.2,6,1.8,virginica
128 | 6.2,2.8,4.8,1.8,virginica
129 | 6.1,3,4.9,1.8,virginica
130 | 6.4,2.8,5.6,2.1,virginica
131 | 7.2,3,5.8,1.6,virginica
132 | 7.4,2.8,6.1,1.9,virginica
133 | 7.9,3.8,6.4,2,virginica
134 | 6.4,2.8,5.6,2.2,virginica
135 | 6.3,2.8,5.1,1.5,virginica
136 | 6.1,2.6,5.6,1.4,virginica
137 | 7.7,3,6.1,2.3,virginica
138 | 6.3,3.4,5.6,2.4,virginica
139 | 6.4,3.1,5.5,1.8,virginica
140 | 6,3,4.8,1.8,virginica
141 | 6.9,3.1,5.4,2.1,virginica
142 | 6.7,3.1,5.6,2.4,virginica
143 | 6.9,3.1,5.1,2.3,virginica
144 | 5.8,2.7,5.1,1.9,virginica
145 | 6.8,3.2,5.9,2.3,virginica
146 | 6.7,3.3,5.7,2.5,virginica
147 | 6.7,3,5.2,2.3,virginica
148 | 6.3,2.5,5,1.9,virginica
149 | 6.5,3,5.2,2,virginica
150 | 6.2,3.4,5.4,2.3,virginica
151 | 5.9,3,5.1,1.8,virginica
152 |
--------------------------------------------------------------------------------