From 18b1a037954d0dad6903f78c9424a8eb7a10d2d5 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Tue, 11 Nov 2025 18:37:14 +0100 Subject: [PATCH] Add Sentiment Analysis lab --- SentimentAnalysisLab/build.gradle | 33 +++++ .../example/senti/ProductReviewAnalyzer.java | 120 ++++++++++++++++++ .../src/main/resources/application.properties | 1 + .../src/main/resources/product_reviews.csv | 11 ++ settings.gradle | 3 +- 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 SentimentAnalysisLab/build.gradle create mode 100644 SentimentAnalysisLab/src/main/java/com/example/senti/ProductReviewAnalyzer.java create mode 100644 SentimentAnalysisLab/src/main/resources/application.properties create mode 100644 SentimentAnalysisLab/src/main/resources/product_reviews.csv diff --git a/SentimentAnalysisLab/build.gradle b/SentimentAnalysisLab/build.gradle new file mode 100644 index 0000000..dc41943 --- /dev/null +++ b/SentimentAnalysisLab/build.gradle @@ -0,0 +1,33 @@ +import org.springframework.boot.gradle.plugin.SpringBootPlugin + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' +apply plugin: 'io.spring.dependency-management' + +description = "Sentiment Analysis Lab" + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom SpringBootPlugin.BOM_COORDINATES + } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + + implementation 'com.opencsv:opencsv:5.7.1' + implementation 'edu.stanford.nlp:stanford-corenlp:4.5.9:models' + implementation 'edu.stanford.nlp:stanford-corenlp:4.5.9' + + runtimeOnly 'org.springframework.boot:spring-boot-devtools' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} \ No newline at end of file diff --git a/SentimentAnalysisLab/src/main/java/com/example/senti/ProductReviewAnalyzer.java b/SentimentAnalysisLab/src/main/java/com/example/senti/ProductReviewAnalyzer.java new file mode 100644 index 0000000..c61de54 --- /dev/null +++ b/SentimentAnalysisLab/src/main/java/com/example/senti/ProductReviewAnalyzer.java @@ -0,0 +1,120 @@ +package com.example.senti; + +import edu.stanford.nlp.ling.CoreAnnotations; +import edu.stanford.nlp.neural.rnn.RNNCoreAnnotations; +import edu.stanford.nlp.pipeline.*; +import edu.stanford.nlp.sentiment.SentimentCoreAnnotations; +import edu.stanford.nlp.util.CoreMap; +import com.opencsv.CSVReader; +import com.opencsv.exceptions.CsvException; +import org.springframework.core.io.ClassPathResource; + + +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.*; + +public class ProductReviewAnalyzer { + + private StanfordCoreNLP pipeline; + + public static void main(String[] args) { + ProductReviewAnalyzer analyzer = new ProductReviewAnalyzer(); + analyzer.initialize(); + + try { + // Load reviews from CSV + List reviews = analyzer.loadReviews("/product_reviews.csv"); + Map sentimentDistribution = new LinkedHashMap<>(); + sentimentDistribution.put("Very Positive", 0); + sentimentDistribution.put("Positive", 0); + sentimentDistribution.put("Neutral", 0); + sentimentDistribution.put("Negative", 0); + sentimentDistribution.put("Very Negative", 0); + + System.out.println("=== Review Sentiment Analysis ==="); + for (String[] review : reviews) { + if (review[0].equals("review_id")) continue; // Skip header + + String sentiment = analyzer.analyzeSentiment(review[1]); + sentimentDistribution.put(sentiment, sentimentDistribution.get(sentiment) + 1); + + System.out.printf("Review %2s: %-60s - %s\n", + review[0], + shortenText(review[1], 55), + sentiment); + } + + // Generate report + analyzer.generateReport(sentimentDistribution, reviews.size() - 1); + + } catch (IOException | CsvException e) { + e.printStackTrace(); + } + } + + public void initialize() { + // Set up pipeline properties + Properties props = new Properties(); + props.setProperty("annotators", "tokenize, ssplit, parse, sentiment"); + props.setProperty("coref.algorithm", "neural"); + this.pipeline = new StanfordCoreNLP(props); + } + + public List loadReviews(String filePath) throws IOException, CsvException { + try (CSVReader reader = new CSVReader(new InputStreamReader(new ClassPathResource(filePath).getInputStream()))) { + return reader.readAll(); + } + } + + public String analyzeSentiment(String text) { + int mainSentiment = 0; + int longest = 0; + + Annotation annotation = pipeline.process(text); + for (CoreMap sentence : annotation.get(CoreAnnotations.SentencesAnnotation.class)) { + int sentiment = RNNCoreAnnotations.getPredictedClass(sentence.get(SentimentCoreAnnotations.SentimentAnnotatedTree.class)); + String partText = sentence.toString(); + + if (partText.length() > longest) { + mainSentiment = sentiment; + longest = partText.length(); + } + } + + // Convert numeric sentiment to text + return switch (mainSentiment) { + case 0 -> "Very Negative"; + case 1 -> "Negative"; + case 2 -> "Neutral"; + case 3 -> "Positive"; + case 4 -> "Very Positive"; + default -> "Neutral"; + }; + } + + public void generateReport(Map sentimentCounts, int totalReviews) { + System.out.println("\n=== Sentiment Analysis Report ==="); + System.out.printf("Total Reviews Analyzed: %d\n\n", totalReviews); + + System.out.println("Sentiment Distribution:"); + for (Map.Entry entry : sentimentCounts.entrySet()) { + double percentage = (entry.getValue() * 100.0) / totalReviews; + System.out.printf("%-12s: %2d reviews (%5.1f%%) %s\n", + entry.getKey(), + entry.getValue(), + percentage, + generateBar(percentage)); + } + } + + private static String generateBar(double percentage) { + int bars = (int) (percentage / 5); + return "[" + new String(new char[bars]).replace("\0", "=") + "]"; + } + + private static String shortenText(String text, int maxLength) { + return text.length() > maxLength ? text.substring(0, maxLength - 3) + "..." : text; + } +} \ No newline at end of file diff --git a/SentimentAnalysisLab/src/main/resources/application.properties b/SentimentAnalysisLab/src/main/resources/application.properties new file mode 100644 index 0000000..fec114a --- /dev/null +++ b/SentimentAnalysisLab/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=senti diff --git a/SentimentAnalysisLab/src/main/resources/product_reviews.csv b/SentimentAnalysisLab/src/main/resources/product_reviews.csv new file mode 100644 index 0000000..7cf387e --- /dev/null +++ b/SentimentAnalysisLab/src/main/resources/product_reviews.csv @@ -0,0 +1,11 @@ +review_id,review_text +1,"This product is absolutely amazing! Works perfectly." +2,"Terrible quality. Broke after 2 days of use." +3,"Good value for the price. Very satisfied." +4,"Not what I expected. Poor packaging." +5,"Excellent customer service and fast shipping." +6,"The item arrived damaged. Very disappointed." +7,"Works well but instructions could be better." +8,"Best purchase I've made this year!" +9,"Overpriced for what you get." +10,"Highly recommend this to all my friends." \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 4f5586a..7e810bd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,4 +7,5 @@ include 'RetailManagementSystem:front-end' include 'InventoryManagementSystem' include 'SmartClinicManagementSystem:app' include 'SoftwareDevChatbot' -include 'RegressionPredictionLab' \ No newline at end of file +include 'RegressionPredictionLab' +include 'SentimentAnalysisLab' \ No newline at end of file