diff --git a/CHANGELOG.md b/CHANGELOG.md
index e3dfb7b1..902d9964 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- [#146](https://github.com/green-code-initiative/creedengo-python/pull/146) Add rule GCI24: Returned SQL results should be limited (avoid SELECT...FROM without LIMIT)
+- Add rule GCI113: Prefer XGBoost over RandomForest for better energy efficiency
### Changed
diff --git a/src/it/java/org/greencodeinitiative/creedengo/python/integration/tests/GCIRulesIT.java b/src/it/java/org/greencodeinitiative/creedengo/python/integration/tests/GCIRulesIT.java
index 38ab00ae..b84cfc3c 100644
--- a/src/it/java/org/greencodeinitiative/creedengo/python/integration/tests/GCIRulesIT.java
+++ b/src/it/java/org/greencodeinitiative/creedengo/python/integration/tests/GCIRulesIT.java
@@ -548,6 +548,28 @@ void testGCI203_compliant() {
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_1H);
}
+ @Test
+ void testGCI113_nonCompliant() {
+ String filePath = "src/GCI113/preferXGBoostOverRandomForestNonCompliant.py";
+ String ruleId = "creedengo-python:GCI113";
+ String ruleMsg = "Prefer XGBoost over RandomForest for better energy efficiency";
+ int[] startLines = new int[]{1, 2, 5, 8, 11};
+ int[] endLines = new int[]{1, 2, 5, 8, 11};
+
+ checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_10MIN);
+ }
+
+ @Test
+ void testGCI113_compliant() {
+ String filePath = "src/GCI113/preferXGBoostOverRandomForestCompliant.py";
+ String ruleId = "creedengo-python:GCI113";
+ String ruleMsg = "Prefer XGBoost over RandomForest for better energy efficiency";
+ int[] startLines = new int[]{};
+ int[] endLines = new int[]{};
+
+ checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_10MIN);
+ }
+
@Test
void testGCI404() {
String filePath = "src/GCI404/avoidListComprehensionInIterations.py";
diff --git a/src/it/test-projects/creedengo-python-plugin-test-project/src/GCI113/preferXGBoostOverRandomForestCompliant.py b/src/it/test-projects/creedengo-python-plugin-test-project/src/GCI113/preferXGBoostOverRandomForestCompliant.py
new file mode 100644
index 00000000..c03a20fa
--- /dev/null
+++ b/src/it/test-projects/creedengo-python-plugin-test-project/src/GCI113/preferXGBoostOverRandomForestCompliant.py
@@ -0,0 +1,16 @@
+from xgboost import XGBClassifier
+from xgboost import XGBRegressor
+from sklearn.ensemble import GradientBoostingClassifier
+
+
+# Compliant: using XGBoost classifier
+model = XGBClassifier(eval_metric="logloss")
+model.fit(X_train, y_train)
+
+# Compliant: using XGBoost regressor
+model2 = XGBRegressor(n_estimators=100)
+model2.fit(X_train, y_train)
+
+# Compliant: using other sklearn classifiers
+model3 = GradientBoostingClassifier(n_estimators=100)
+model3.fit(X_train, y_train)
diff --git a/src/it/test-projects/creedengo-python-plugin-test-project/src/GCI113/preferXGBoostOverRandomForestNonCompliant.py b/src/it/test-projects/creedengo-python-plugin-test-project/src/GCI113/preferXGBoostOverRandomForestNonCompliant.py
new file mode 100644
index 00000000..fb21529b
--- /dev/null
+++ b/src/it/test-projects/creedengo-python-plugin-test-project/src/GCI113/preferXGBoostOverRandomForestNonCompliant.py
@@ -0,0 +1,8 @@
+from sklearn.ensemble import RandomForestClassifier # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}
+from sklearn.ensemble import RandomForestRegressor # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}
+
+model = RandomForestClassifier(n_estimators=100) # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}
+
+model2 = RandomForestRegressor(n_estimators=50) # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}
+
+model3 = sklearn.ensemble.RandomForestClassifier(n_estimators=100) # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}
diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java b/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java
index a0bb6d86..47b662ed 100644
--- a/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java
+++ b/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java
@@ -56,7 +56,8 @@ public record PythonRuleRepository(SonarRuntime sonarRuntime) implements RulesDe
GCI104AvoidCreatingTensorUsingNumpyOrNativePython.class,
GCI110AvoidWildcardImportsCheck.class,
GCI109AvoidExceptionsForControlFlowCheck.class,
- GCI112UsingSlotsOnDataClasses.class
+ GCI112UsingSlotsOnDataClasses.class,
+ GCI113PreferXGBoostOverRandomForest.class
);
public static final String LANGUAGE = "py";
diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/checks/GCI113PreferXGBoostOverRandomForest.java b/src/main/java/org/greencodeinitiative/creedengo/python/checks/GCI113PreferXGBoostOverRandomForest.java
new file mode 100644
index 00000000..d5d7107a
--- /dev/null
+++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/GCI113PreferXGBoostOverRandomForest.java
@@ -0,0 +1,76 @@
+/*
+ * creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs
+ * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.greencodeinitiative.creedengo.python.checks;
+
+import org.sonar.check.Rule;
+import org.sonar.plugins.python.api.PythonSubscriptionCheck;
+import org.sonar.plugins.python.api.SubscriptionContext;
+import org.sonar.plugins.python.api.tree.CallExpression;
+import org.sonar.plugins.python.api.tree.Expression;
+import org.sonar.plugins.python.api.tree.ImportFrom;
+import org.sonar.plugins.python.api.tree.Name;
+import org.sonar.plugins.python.api.tree.QualifiedExpression;
+import org.sonar.plugins.python.api.tree.Tree;
+
+@Rule(key = "GCI113")
+public class GCI113PreferXGBoostOverRandomForest extends PythonSubscriptionCheck {
+
+ private static final String DESCRIPTION = "Prefer XGBoost over RandomForest for better energy efficiency";
+ private static final String RANDOM_FOREST_CLASSIFIER = "RandomForestClassifier";
+ private static final String RANDOM_FOREST_REGRESSOR = "RandomForestRegressor";
+
+ @Override
+ public void initialize(Context context) {
+ context.registerSyntaxNodeConsumer(Tree.Kind.IMPORT_FROM, this::checkImport);
+ context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, this::checkInstantiation);
+ }
+
+ private void checkImport(SubscriptionContext context) {
+ ImportFrom importFrom = (ImportFrom) context.syntaxNode();
+ if (importFrom.importedNames() == null) {
+ return;
+ }
+ importFrom.importedNames().forEach(aliasedName -> {
+ String importedName = aliasedName.dottedName().names().stream()
+ .map(Name::name)
+ .reduce((a, b) -> a + "." + b)
+ .orElse("");
+ if (RANDOM_FOREST_CLASSIFIER.equals(importedName) || RANDOM_FOREST_REGRESSOR.equals(importedName)) {
+ context.addIssue(aliasedName, DESCRIPTION);
+ }
+ });
+ }
+
+ private void checkInstantiation(SubscriptionContext context) {
+ CallExpression callExpression = (CallExpression) context.syntaxNode();
+ Expression callee = callExpression.callee();
+
+ if (callee.is(Tree.Kind.NAME)) {
+ Name name = (Name) callee;
+ if (RANDOM_FOREST_CLASSIFIER.equals(name.name()) || RANDOM_FOREST_REGRESSOR.equals(name.name())) {
+ context.addIssue(callExpression, DESCRIPTION);
+ }
+ } else if (callee.is(Tree.Kind.QUALIFIED_EXPR)) {
+ QualifiedExpression qualifiedExpr = (QualifiedExpression) callee;
+ String methodName = qualifiedExpr.name().name();
+ if (RANDOM_FOREST_CLASSIFIER.equals(methodName) || RANDOM_FOREST_REGRESSOR.equals(methodName)) {
+ context.addIssue(callExpression, DESCRIPTION);
+ }
+ }
+ }
+}
diff --git a/src/main/resources/org/greencodeinitiative/creedengo/python/creedengo_way_profile.json b/src/main/resources/org/greencodeinitiative/creedengo/python/creedengo_way_profile.json
index 6a107ce8..5d8559b7 100644
--- a/src/main/resources/org/greencodeinitiative/creedengo/python/creedengo_way_profile.json
+++ b/src/main/resources/org/greencodeinitiative/creedengo/python/creedengo_way_profile.json
@@ -27,6 +27,7 @@
"GCI110",
"GCI111",
"GCI112",
+ "GCI113",
"GCI203",
"GCI404"
]
diff --git a/src/test/java/org/greencodeinitiative/creedengo/python/checks/GCI113PreferXGBoostOverRandomForestTest.java b/src/test/java/org/greencodeinitiative/creedengo/python/checks/GCI113PreferXGBoostOverRandomForestTest.java
new file mode 100644
index 00000000..e6dc6afe
--- /dev/null
+++ b/src/test/java/org/greencodeinitiative/creedengo/python/checks/GCI113PreferXGBoostOverRandomForestTest.java
@@ -0,0 +1,34 @@
+/*
+ * creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs
+ * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.greencodeinitiative.creedengo.python.checks;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.python.checks.utils.PythonCheckVerifier;
+
+class GCI113PreferXGBoostOverRandomForestTest {
+
+ @Test
+ void testNonCompliant() {
+ PythonCheckVerifier.verify(System.getProperty("testfiles.path") + "/GCI113/preferXGBoostOverRandomForestNonCompliant.py", new GCI113PreferXGBoostOverRandomForest());
+ }
+
+ @Test
+ void testCompliant() {
+ PythonCheckVerifier.verifyNoIssue(System.getProperty("testfiles.path") + "/GCI113/preferXGBoostOverRandomForestCompliant.py", new GCI113PreferXGBoostOverRandomForest());
+ }
+}