From d72ed9ad11ae9b7879ae558bb581064601e1f26a Mon Sep 17 00:00:00 2001 From: Ali Dehghani Date: Sun, 4 Aug 2019 20:29:02 +0430 Subject: [PATCH 1/5] Added the K-Means related codes. --- machine-learning/pom.xml | 34 + .../java/com/baeldung/ml/kmeans/Centroid.java | 41 + .../java/com/baeldung/ml/kmeans/Distance.java | 20 + .../java/com/baeldung/ml/kmeans/Errors.java | 23 + .../baeldung/ml/kmeans/EuclideanDistance.java | 25 + .../java/com/baeldung/ml/kmeans/KMeans.java | 211 + .../java/com/baeldung/ml/kmeans/LastFm.java | 113 + .../com/baeldung/ml/kmeans/LastFmService.java | 101 + .../java/com/baeldung/ml/kmeans/Record.java | 60 + .../src/main/resources/kmeans/artists.json | 3384 +++++++++++++++++ .../src/main/resources/kmeans/lastfm.json | 490 +++ .../src/main/resources/kmeans/radial.html | 54 + pom.xml | 1 + 13 files changed, 4557 insertions(+) create mode 100644 machine-learning/pom.xml create mode 100644 machine-learning/src/main/java/com/baeldung/ml/kmeans/Centroid.java create mode 100644 machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java create mode 100644 machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java create mode 100644 machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java create mode 100644 machine-learning/src/main/java/com/baeldung/ml/kmeans/KMeans.java create mode 100644 machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java create mode 100644 machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java create mode 100644 machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java create mode 100644 machine-learning/src/main/resources/kmeans/artists.json create mode 100644 machine-learning/src/main/resources/kmeans/lastfm.json create mode 100644 machine-learning/src/main/resources/kmeans/radial.html diff --git a/machine-learning/pom.xml b/machine-learning/pom.xml new file mode 100644 index 0000000000..2753de2ff6 --- /dev/null +++ b/machine-learning/pom.xml @@ -0,0 +1,34 @@ + + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + + 4.0.0 + machine-learning + 0.0.1-SNAPSHOT + Machine Learning + Host for all Machine Learning Algorithms + + + + com.squareup.retrofit2 + retrofit + ${retrofit.version} + + + com.squareup.retrofit2 + converter-jackson + ${retrofit.version} + + + + + 2.6.0 + + \ No newline at end of file diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Centroid.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Centroid.java new file mode 100644 index 0000000000..922a19d861 --- /dev/null +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Centroid.java @@ -0,0 +1,41 @@ +package com.baeldung.ml.kmeans; + +import java.util.Map; +import java.util.Objects; + +/** + * Encapsulates all coordinates for a particular cluster centroid. + */ +public class Centroid { + + /** + * The centroid coordinates. + */ + private final Map coordinates; + + public Centroid(Map coordinates) { + this.coordinates = coordinates; + } + + public Map getCoordinates() { + return coordinates; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Centroid centroid = (Centroid) o; + return Objects.equals(getCoordinates(), centroid.getCoordinates()); + } + + @Override + public int hashCode() { + return Objects.hash(getCoordinates()); + } + + @Override + public String toString() { + return "Centroid " + coordinates; + } +} diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java new file mode 100644 index 0000000000..37e1c8492e --- /dev/null +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java @@ -0,0 +1,20 @@ +package com.baeldung.ml.kmeans; + +import java.util.Map; + +/** + * Defines a contract to calculate distance between two feature vectors. The less the + * calculated distance, the more two items are similar to each other. + */ +public interface Distance { + + /** + * Calculates the distance between two feature vectors. + * + * @param f1 The first set of features. + * @param f2 The second set of features. + * @return Calculated distance. + * @throws IllegalArgumentException If the given feature vectors are invalid. + */ + double calculate(Map f1, Map f2); +} diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java new file mode 100644 index 0000000000..4b973c1146 --- /dev/null +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java @@ -0,0 +1,23 @@ +package com.baeldung.ml.kmeans; + +import java.util.List; +import java.util.Map; + +/** + * Encapsulates methods to calculates errors between centroid and the cluster members. + */ +public class Errors { + + public static double sse(Map> clustered, Distance distance) { + double sum = 0; + for (Map.Entry> entry : clustered.entrySet()) { + Centroid centroid = entry.getKey(); + for (Record record : entry.getValue()) { + double d = distance.calculate(centroid.getCoordinates(), record.getFeatures()); + sum += Math.pow(d, 2); + } + } + + return sum; + } +} diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java new file mode 100644 index 0000000000..1946c508b4 --- /dev/null +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java @@ -0,0 +1,25 @@ +package com.baeldung.ml.kmeans; + +import java.util.Map; + +/** + * Calculates the distance between two items using the Euclidean formula. + */ +public class EuclideanDistance implements Distance { + + @Override + public double calculate(Map f1, Map f2) { + if (f1 == null || f2 == null) + throw new IllegalArgumentException("Feature vectors can't be null"); + + double sum = 0; + for (String key : f1.keySet()) { + Double v1 = f1.get(key); + Double v2 = f2.get(key); + + if (v1 != null && v2 != null) sum += Math.pow(v1 - v2, 2); + } + + return Math.sqrt(sum); + } +} diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/KMeans.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/KMeans.java new file mode 100644 index 0000000000..6b9e513a95 --- /dev/null +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/KMeans.java @@ -0,0 +1,211 @@ +package com.baeldung.ml.kmeans; + +import java.util.*; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; + +/** + * Encapsulates an implementation of KMeans clustering algorithm. + * + * @author Ali Dehghani + */ +public class KMeans { + + private KMeans() { + throw new IllegalAccessError("You shouldn't call this constructor"); + } + + /** + * Will be used to generate random numbers. + */ + private static final Random random = new Random(); + + /** + * Performs the K-Means clustering algorithm on the given dataset. + * + * @param records The dataset. + * @param k Number of Clusters. + * @param distance To calculate the distance between two items. + * @param maxIterations Upper bound for the number of iterations. + * @return K clusters along with their features. + */ + public static Map> fit(List records, int k, Distance distance, int maxIterations) { + applyPreconditions(records, k, distance, maxIterations); + + List centroids = randomCentroids(records, k); + Map> clusters = new HashMap<>(); + Map> lastState = new HashMap<>(); + + // iterate for a pre-defined number of times + for (int i = 0; i < maxIterations; i++) { + boolean isLastIteration = i == maxIterations - 1; + + // in each iteration we should find the nearest centroid for each record + for (Record record : records) { + Centroid centroid = nearestCentroid(record, centroids, distance); + assignToCluster(clusters, record, centroid); + } + + // if the assignment does not change, then the algorithm terminates + boolean shouldTerminate = isLastIteration || clusters.equals(lastState); + lastState = clusters; + if (shouldTerminate) break; + + // at the end of each iteration we should relocate the centroids + centroids = relocateCentroids(clusters); + clusters = new HashMap<>(); + } + + return lastState; + } + + /** + * Move all cluster centroids to the average of all assigned features. + * + * @param clusters The current cluster configuration. + * @return Collection of new and relocated centroids. + */ + private static List relocateCentroids(Map> clusters) { + return clusters.entrySet().stream() + .map(e -> average(e.getKey(), e.getValue())).collect(toList()); + } + + /** + * Moves the given centroid to the average position of all assigned features. If + * the centroid has no feature in its cluster, then there would be no need for a + * relocation. Otherwise, for each entry we calculate the average of all records + * first by summing all the entries and then dividing the final summation value by + * the number of records. + * + * @param centroid The centroid to move. + * @param records The assigned features. + * @return The moved centroid. + */ + private static Centroid average(Centroid centroid, List records) { + // if this cluster is empty, then we shouldn't move the centroid + if (records == null || records.isEmpty()) return centroid; + + // Since some records don't have all possible attributes, we initialize + // average coordinates equal to current centroid coordinates + Map average = centroid.getCoordinates(); + + // The average function works correctly if we clear all coordinates corresponding + // to present record attributes + records.stream().flatMap(e -> e.getFeatures().keySet().stream()) + .forEach(k -> average.put(k, 0.0)); + + for (Record record : records) { + record.getFeatures().forEach( + (k, v) -> average.compute(k, (k1, currentValue) -> v + currentValue) + ); + } + + average.forEach((k, v) -> average.put(k, v / records.size())); + + return new Centroid(average); + } + + /** + * Assigns a feature vector to the given centroid. If this is the first assignment for this centroid, + * first we should create the list. + * + * @param clusters The current cluster configuration. + * @param record The feature vector. + * @param centroid The centroid. + */ + private static void assignToCluster(Map> clusters, + Record record, Centroid centroid) { + clusters.compute(centroid, (key, list) -> { + if (list == null) { + list = new ArrayList<>(); + } + + list.add(record); + return list; + }); + } + + /** + * With the help of the given distance calculator, iterates through centroids and finds the + * nearest one to the given record. + * + * @param record The feature vector to find a centroid for. + * @param centroids Collection of all centroids. + * @param distance To calculate the distance between two items. + * @return The nearest centroid to the given feature vector. + */ + private static Centroid nearestCentroid(Record record, List centroids, + Distance distance) { + double minimumDistance = Double.MAX_VALUE; + Centroid nearest = null; + + for (Centroid centroid : centroids) { + double currentDistance = distance.calculate(record.getFeatures(), centroid.getCoordinates()); + + if (currentDistance < minimumDistance) { + minimumDistance = currentDistance; + nearest = centroid; + } + } + + return nearest; + } + + /** + * Generates k random centroids. Before kicking-off the centroid generation process, + * first we calculate the possible value range for each attribute. Then when + * we're going to generate the centroids, we generate random coordinates in + * the [min, max] range for each attribute. + * + * @param records The dataset which helps to calculate the [min, max] range for + * each attribute. + * @param k Number of clusters. + * @return Collections of randomly generated centroids. + */ + private static List randomCentroids(List records, int k) { + List centroids = new ArrayList<>(); + Map maxs = new HashMap<>(); + Map mins = new HashMap<>(); + + for (Record record : records) { + record.getFeatures().forEach((key, value) -> { + // compares the value with the current max and choose the bigger value between them + maxs.compute(key, (k1, max) -> max == null || value > max ? value : max); + + // compare the value with the current min and choose the smaller value between them + mins.compute(key, (k1, min) -> min == null || value < min ? value : min); + }); + } + + Set attributes = records.stream() + .flatMap(e -> e.getFeatures().keySet().stream()).collect(toSet()); + for (int i = 0; i < k; i++) { + Map coordinates = new HashMap<>(); + for (String attribute : attributes) { + double max = maxs.get(attribute); + double min = mins.get(attribute); + coordinates.put(attribute, random.nextDouble() * (max - min) + min); + } + + centroids.add(new Centroid(coordinates)); + } + + return centroids; + } + + private static void applyPreconditions(List records, int k, + Distance distance, int maxIterations) { + if (records == null || records.isEmpty()) + throw new IllegalArgumentException("The dataset can't be empty"); + + if (k <= 1) + throw new IllegalArgumentException("It doesn't make sense to have less than or equal to 1 cluster"); + + if (distance == null) + throw new IllegalArgumentException("The distance calculator is required"); + + if (maxIterations <= 0) + throw new IllegalArgumentException("Max iterations should be a positive number"); + } +} diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java new file mode 100644 index 0000000000..cdaf2170cd --- /dev/null +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java @@ -0,0 +1,113 @@ +package com.baeldung.ml.kmeans; + +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toSet; + +public class LastFm { + + private static OkHttpClient okHttp = new OkHttpClient.Builder() + .addInterceptor(new LastFmService.Authenticator("put your API key here")) + .build(); + + private static Retrofit retrofit = new Retrofit.Builder().client(okHttp) + .addConverterFactory(JacksonConverterFactory.create()) + .baseUrl("http://ws.audioscrobbler.com/") + .build(); + + private static LastFmService lastFm = retrofit.create(LastFmService.class); + + private static ObjectMapper mapper = new ObjectMapper(); + + public static void main(String[] args) throws IOException { + List artists = getTop100Artists(); + Set tags = getTop100Tags(); + List records = datasetWithTaggedArtists(artists, tags); + + Map> clusters = KMeans.fit(records, 7, new EuclideanDistance(), 1000); + // Print the cluster configuration + clusters.forEach((key, value) -> { + System.out.println("------------------------------ CLUSTER -----------------------------------"); + + System.out.println(sortedCentroid(key)); + String members = String.join(", ", value.stream().map(Record::getDescription).collect(toSet())); + System.out.print(members); + + System.out.println(); + System.out.println(); + }); + + Map json = convertToD3CompatibleMap(clusters); + System.out.println(mapper.writeValueAsString(json)); + } + + private static Map convertToD3CompatibleMap(Map> clusters) { + Map json = new HashMap<>(); + json.put("name", "Musicians"); + List> children = new ArrayList<>(); + clusters.forEach((key, value) -> { + Map child = new HashMap<>(); + child.put("name", dominantGenre(sortedCentroid(key))); + List> nested = new ArrayList<>(); + for (Record record : value) { + nested.add(Collections.singletonMap("name", record.getDescription())); + } + child.put("children", nested); + + + children.add(child); + }); + json.put("children", children); + return json; + } + + private static String dominantGenre(Centroid centroid) { + return centroid.getCoordinates().keySet().stream().limit(2).collect(Collectors.joining(", ")); + } + + private static Centroid sortedCentroid(Centroid key) { + List> entries = new ArrayList<>(key.getCoordinates().entrySet()); + entries.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue())); + + Map sorted = new LinkedHashMap<>(); + for (Map.Entry entry : entries) { + sorted.put(entry.getKey(), entry.getValue()); + } + + return new Centroid(sorted); + } + + private static List datasetWithTaggedArtists(List artists, + Set topTags) throws IOException { + List records = new ArrayList<>(); + for (String artist : artists) { + Map tags = lastFm.topTagsFor(artist).execute().body().all(); + + // Only keep popular tags. + tags.entrySet().removeIf(e -> !topTags.contains(e.getKey())); + + records.add(new Record(artist, tags)); + } + return records; + } + + private static Set getTop100Tags() throws IOException { + return lastFm.topTags().execute().body().all(); + } + + private static List getTop100Artists() throws IOException { + List artists = new ArrayList<>(); + for (int i = 1; i <= 2; i++) { + artists.addAll(lastFm.topArtists(i).execute().body().all()); + } + + return artists; + } +} diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java new file mode 100644 index 0000000000..0cc8e9e285 --- /dev/null +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java @@ -0,0 +1,101 @@ +package com.baeldung.ml.kmeans; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +import static java.util.stream.Collectors.toList; + +public interface LastFmService { + + @GET("/2.0/?method=chart.gettopartists&format=json&limit=50") + Call topArtists(@Query("page") int page); + + @GET("/2.0/?method=artist.gettoptags&format=json&limit=20&autocorrect=1") + Call topTagsFor(@Query("artist") String artist); + + @GET("/2.0/?method=chart.gettoptags&format=json&limit=100") + Call topTags(); + + /** + * HTTP interceptor to intercept all HTTP requests and add the API key to them. + */ + class Authenticator implements Interceptor { + + private final String apiKey; + + Authenticator(String apiKey) { + this.apiKey = apiKey; + } + + @Override + public Response intercept(Chain chain) throws IOException { + HttpUrl url = chain.request().url().newBuilder().addQueryParameter("api_key", apiKey).build(); + Request request = chain.request().newBuilder().url(url).build(); + + return chain.proceed(request); + } + } + + @JsonAutoDetect(fieldVisibility = ANY) + class TopTags { + + private Map tags; + + @SuppressWarnings("unchecked") + public Set all() { + List> topTags = (List>) tags.get("tag"); + return topTags.stream().map(e -> ((String) e.get("name"))).collect(Collectors.toSet()); + } + } + + @JsonAutoDetect(fieldVisibility = ANY) + class Tags { + + @JsonProperty("toptags") + private Map topTags; + + @SuppressWarnings("unchecked") + public Map all() { + try { + Map all = new HashMap<>(); + List> tags = (List>) topTags.get("tag"); + for (Map tag : tags) { + all.put(((String) tag.get("name")), ((Integer) tag.get("count")).doubleValue()); + } + + return all; + } catch (Exception e) { + return Collections.emptyMap(); + } + } + } + + @JsonAutoDetect(fieldVisibility = ANY) + class Artists { + + private Map artists; + + @SuppressWarnings("unchecked") + public List all() { + try { + List> artists = (List>) this.artists.get("artist"); + return artists.stream().map(e -> ((String) e.get("name"))).collect(toList()); + } catch (Exception e) { + return Collections.emptyList(); + } + } + } +} diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java new file mode 100644 index 0000000000..7208526136 --- /dev/null +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java @@ -0,0 +1,60 @@ +package com.baeldung.ml.kmeans; + +import java.util.Map; +import java.util.Objects; + +/** + * Encapsulates all feature values for a few attributes. Optionally each record + * can be described with the {@link #description} field. + */ +public class Record { + + /** + * The record description. For example, this can be the artist name for the famous musician + * example. + */ + private final String description; + + /** + * Encapsulates all attributes and their corresponding values, i.e. features. + */ + private final Map features; + + public Record(String description, Map features) { + this.description = description; + this.features = features; + } + + public Record(Map features) { + this("", features); + } + + public String getDescription() { + return description; + } + + public Map getFeatures() { + return features; + } + + @Override + public String toString() { + String prefix = description == null || description.trim().isEmpty() ? "Record" : description; + + return prefix + ": " + features; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Record record = (Record) o; + return Objects.equals(getDescription(), record.getDescription()) && + Objects.equals(getFeatures(), record.getFeatures()); + } + + @Override + public int hashCode() { + return Objects.hash(getDescription(), getFeatures()); + } +} diff --git a/machine-learning/src/main/resources/kmeans/artists.json b/machine-learning/src/main/resources/kmeans/artists.json new file mode 100644 index 0000000000..6cee0d1de2 --- /dev/null +++ b/machine-learning/src/main/resources/kmeans/artists.json @@ -0,0 +1,3384 @@ +[ + { + "description": "Billie Eilish", + "features": { + "singer-songwriter": 3.0, + "american": 23.0, + "british": 3.0, + "experimental": 3.0, + "rnb": 3.0, + "female vocalists": 49.0, + "indie": 58.0, + "chillout": 12.0, + "ambient": 3.0, + "pop": 100.0, + "synthpop": 3.0, + "electronic": 58.0, + "indie pop": 83.0, + "seen live": 18.0, + "alternative": 40.0, + "female vocalist": 9.0 + } + }, + { + "description": "Ed Sheeran", + "features": { + "indie": 12.0, + "singer-songwriter": 78.0, + "male vocalists": 13.0, + "soul": 3.0, + "chillout": 2.0, + "Mellow": 2.0, + "british": 82.0, + "pop": 23.0, + "rock": 4.0, + "rap": 2.0, + "indie pop": 3.0, + "seen live": 21.0, + "Hip-Hop": 4.0, + "acoustic": 100.0, + "alternative": 8.0, + "guitar": 2.0, + "00s": 2.0, + "Soundtrack": 1.0, + "Love": 2.0, + "folk": 12.0 + } + }, + { + "description": "Lil Nas X", + "features": { + "country": 54.0, + "male vocalists": 8.0, + "american": 24.0, + "rock": 16.0, + "pop": 8.0, + "hip hop": 47.0, + "rap": 62.0, + "Hip-Hop": 100.0, + "alternative": 24.0 + } + }, + { + "description": "Queen", + "features": { + "favorites": 2.0, + "male vocalists": 3.0, + "british": 17.0, + "classic rock": 100.0, + "rock": 81.0, + "pop": 7.0, + "heavy metal": 2.0, + "oldies": 2.0, + "seen live": 2.0, + "alternative": 4.0, + "metal": 2.0, + "Progressive rock": 6.0, + "alternative rock": 1.0, + "hard rock": 38.0, + "70s": 8.0, + "80s": 41.0, + "90s": 2.0 + } + }, + { + "description": "Ariana Grande", + "features": { + "american": 13.0, + "beautiful": 2.0, + "dance": 11.0, + "House": 2.0, + "Soundtrack": 3.0, + "chillout": 3.0, + "pop": 100.0, + "rock": 2.0, + "electronic": 3.0, + "seen live": 3.0, + "female vocalist": 3.0, + "singer-songwriter": 5.0, + "rnb": 51.0, + "female vocalists": 54.0, + "soul": 10.0, + "synthpop": 2.0, + "hip hop": 3.0, + "Hip-Hop": 3.0 + } + }, + { + "description": "Post Malone", + "features": { + "american": 8.0, + "rap": 72.0, + "rnb": 14.0, + "pop": 8.0, + "synthpop": 3.0, + "hip hop": 35.0, + "seen live": 19.0, + "Hip-Hop": 100.0 + } + }, + { + "description": "Drake", + "features": { + "indie": 4.0, + "male vocalists": 5.0, + "soul": 2.0, + "pop": 6.0, + "hip hop": 37.0, + "rap": 72.0, + "seen live": 8.0, + "Hip-Hop": 100.0, + "rnb": 50.0, + "00s": 1.0 + } + }, + { + "description": "Kanye West", + "features": { + "indie": 2.0, + "male vocalists": 3.0, + "soul": 4.0, + "american": 6.0, + "experimental": 2.0, + "pop": 6.0, + "rock": 2.0, + "electronica": 1.0, + "hip hop": 40.0, + "electronic": 3.0, + "dance": 2.0, + "rap": 69.0, + "seen live": 22.0, + "Hip-Hop": 100.0, + "rnb": 31.0, + "alternative": 3.0, + "00s": 3.0 + } + }, + { + "description": "The Beatles", + "features": { + "indie": 3.0, + "favorites": 2.0, + "male vocalists": 2.0, + "singer-songwriter": 2.0, + "Psychedelic Rock": 6.0, + "british": 58.0, + "experimental": 2.0, + "classic rock": 100.0, + "rock": 71.0, + "pop": 41.0, + "psychedelic": 13.0, + "indie rock": 2.0, + "oldies": 10.0, + "britpop": 4.0, + "alternative": 4.0, + "Progressive rock": 2.0, + "alternative rock": 1.0, + "Love": 2.0, + "folk": 1.0, + "60s": 56.0, + "70s": 3.0 + } + }, + { + "description": "Taylor Swift", + "features": { + "country": 100.0, + "singer-songwriter": 42.0, + "chillout": 2.0, + "american": 8.0, + "pop": 71.0, + "rock": 2.0, + "beautiful": 2.0, + "synthpop": 2.0, + "seen live": 4.0, + "acoustic": 31.0, + "female vocalist": 3.0, + "00s": 3.0, + "female vocalists": 60.0, + "Love": 3.0, + "folk": 2.0 + } + }, + { + "description": "Radiohead", + "features": { + "indie": 59.0, + "favorites": 2.0, + "male vocalists": 2.0, + "emo": 1.0, + "chillout": 2.0, + "british": 22.0, + "experimental": 14.0, + "ambient": 2.0, + "classic rock": 1.0, + "rock": 73.0, + "electronica": 5.0, + "psychedelic": 3.0, + "pop": 2.0, + "beautiful": 1.0, + "post-rock": 3.0, + "indie rock": 15.0, + "electronic": 48.0, + "trip-hop": 1.0, + "britpop": 17.0, + "indie pop": 1.0, + "seen live": 27.0, + "alternative": 100.0, + "Progressive rock": 6.0, + "alternative rock": 81.0, + "00s": 2.0, + "idm": 1.0, + "90s": 5.0 + } + }, + { + "description": "Beyoncé", + "features": { + "singer-songwriter": 3.0, + "jazz": 2.0, + "american": 9.0, + "beautiful": 2.0, + "dance": 13.0, + "rap": 2.0, + "rnb": 100.0, + "female vocalists": 70.0, + "Love": 3.0, + "soul": 56.0, + "pop": 91.0, + "hip hop": 5.0, + "electronic": 2.0, + "seen live": 6.0, + "Hip-Hop": 45.0, + "alternative": 1.0, + "female vocalist": 4.0, + "00s": 3.0, + "funk": 2.0 + } + }, + { + "description": "Shawn Mendes", + "features": { + "male vocalists": 28.0, + "singer-songwriter": 13.0, + "british": 7.0, + "rnb": 7.0, + "acoustic": 37.0, + "metal": 4.0, + "folk": 37.0, + "indie": 10.0, + "soul": 4.0, + "pop": 100.0, + "seen live": 13.0, + "alternative": 4.0, + "alternative rock": 4.0 + } + }, + { + "description": "David Bowie", + "features": { + "singer-songwriter": 11.0, + "male vocalists": 4.0, + "Psychedelic Rock": 2.0, + "british": 23.0, + "experimental": 9.0, + "classic rock": 86.0, + "psychedelic": 3.0, + "electronica": 2.0, + "indie rock": 2.0, + "dance": 2.0, + "post-punk": 2.0, + "punk": 2.0, + "hard rock": 3.0, + "folk": 2.0, + "70s": 15.0, + "90s": 4.0, + "indie": 6.0, + "favorites": 2.0, + "soul": 2.0, + "ambient": 2.0, + "rock": 100.0, + "pop": 19.0, + "new wave": 7.0, + "electronic": 4.0, + "oldies": 2.0, + "britpop": 2.0, + "seen live": 11.0, + "alternative": 43.0, + "Progressive rock": 4.0, + "alternative rock": 6.0, + "industrial": 2.0, + "00s": 2.0, + "60s": 3.0, + "80s": 48.0, + "funk": 2.0 + } + }, + { + "description": "Kendrick Lamar", + "features": { + "male vocalists": 2.0, + "american": 7.0, + "hip hop": 17.0, + "rap": 70.0, + "seen live": 38.0, + "Hip-Hop": 100.0, + "funk": 2.0 + } + }, + { + "description": "Rihanna", + "features": { + "american": 4.0, + "beautiful": 2.0, + "dance": 56.0, + "rap": 2.0, + "rnb": 89.0, + "House": 1.0, + "female vocalists": 63.0, + "Love": 2.0, + "soul": 4.0, + "pop": 100.0, + "rock": 2.0, + "hip hop": 6.0, + "electronic": 3.0, + "seen live": 7.0, + "Hip-Hop": 41.0, + "alternative": 1.0, + "female vocalist": 4.0, + "00s": 3.0, + "reggae": 6.0 + } + }, + { + "description": "Arctic Monkeys", + "features": { + "indie": 98.0, + "male vocalists": 2.0, + "Psychedelic Rock": 1.0, + "british": 75.0, + "rock": 63.0, + "pop": 2.0, + "punk rock": 1.0, + "indie rock": 100.0, + "britpop": 16.0, + "indie pop": 2.0, + "seen live": 45.0, + "alternative": 60.0, + "post-punk": 3.0, + "alternative rock": 19.0, + "00s": 3.0, + "punk": 2.0 + } + }, + { + "description": "Tyler, the Creator", + "features": { + "experimental": 4.0, + "american": 4.0, + "hip hop": 12.0, + "rap": 55.0, + "seen live": 15.0, + "Hip-Hop": 100.0, + "alternative": 2.0 + } + }, + { + "description": "Lana Del Rey", + "features": { + "singer-songwriter": 10.0, + "jazz": 2.0, + "american": 14.0, + "beautiful": 2.0, + "indie rock": 2.0, + "female vocalists": 100.0, + "folk": 2.0, + "indie": 93.0, + "soul": 3.0, + "chillout": 3.0, + "blues": 2.0, + "pop": 80.0, + "rock": 2.0, + "electronic": 2.0, + "trip-hop": 10.0, + "indie pop": 88.0, + "seen live": 12.0, + "alternative": 67.0, + "female vocalist": 4.0, + "alternative rock": 2.0 + } + }, + { + "description": "Katy Perry", + "features": { + "indie": 25.0, + "singer-songwriter": 4.0, + "american": 9.0, + "pop": 100.0, + "rock": 24.0, + "beautiful": 2.0, + "indie rock": 1.0, + "electronic": 4.0, + "dance": 9.0, + "indie pop": 1.0, + "seen live": 7.0, + "rnb": 1.0, + "alternative": 3.0, + "female vocalist": 5.0, + "00s": 3.0, + "female vocalists": 62.0, + "Love": 2.0 + } + }, + { + "description": "Lady Gaga", + "features": { + "singer-songwriter": 4.0, + "american": 7.0, + "pop": 100.0, + "electronica": 3.0, + "rock": 2.0, + "electro": 2.0, + "synthpop": 2.0, + "electronic": 53.0, + "dance": 73.0, + "seen live": 8.0, + "techno": 1.0, + "rnb": 1.0, + "alternative": 1.0, + "female vocalist": 21.0, + "00s": 3.0, + "female vocalists": 43.0, + "Love": 2.0 + } + }, + { + "description": "Tame Impala", + "features": { + "indie": 21.0, + "male vocalists": 1.0, + "Psychedelic Rock": 100.0, + "experimental": 4.0, + "psychedelic": 92.0, + "rock": 41.0, + "indie rock": 60.0, + "electronic": 3.0, + "indie pop": 2.0, + "seen live": 53.0, + "alternative": 7.0, + "Progressive rock": 3.0, + "alternative rock": 3.0, + "00s": 3.0 + } + }, + { + "description": "Calvin Harris", + "features": { + "indie": 8.0, + "male vocalists": 3.0, + "singer-songwriter": 1.0, + "british": 15.0, + "electronica": 34.0, + "pop": 8.0, + "electro": 53.0, + "synthpop": 4.0, + "electronic": 100.0, + "dance": 75.0, + "indie pop": 2.0, + "seen live": 36.0, + "techno": 1.0, + "alternative": 1.0, + "House": 9.0, + "00s": 3.0, + "80s": 1.0 + } + }, + { + "description": "Red Hot Chili Peppers", + "features": { + "indie": 5.0, + "favorites": 1.0, + "male vocalists": 1.0, + "american": 5.0, + "classic rock": 3.0, + "rock": 100.0, + "pop": 2.0, + "punk rock": 2.0, + "indie rock": 2.0, + "seen live": 23.0, + "Grunge": 2.0, + "alternative": 54.0, + "metal": 2.0, + "alternative rock": 59.0, + "00s": 2.0, + "punk": 6.0, + "hard rock": 2.0, + "80s": 2.0, + "funk": 50.0, + "90s": 5.0 + } + }, + { + "description": "Coldplay", + "features": { + "indie": 55.0, + "favorites": 2.0, + "male vocalists": 3.0, + "singer-songwriter": 2.0, + "emo": 2.0, + "Mellow": 3.0, + "chillout": 3.0, + "british": 28.0, + "classic rock": 2.0, + "rock": 100.0, + "pop": 18.0, + "piano": 3.0, + "indie rock": 11.0, + "electronic": 2.0, + "britpop": 81.0, + "indie pop": 2.0, + "seen live": 22.0, + "alternative": 87.0, + "acoustic": 2.0, + "alternative rock": 68.0, + "00s": 2.0, + "Love": 2.0, + "90s": 2.0 + } + }, + { + "description": "Miley Cyrus", + "features": { + "country": 3.0, + "singer-songwriter": 1.0, + "american": 12.0, + "beautiful": 2.0, + "grindcore": 1.0, + "dance": 42.0, + "rnb": 4.0, + "Soundtrack": 2.0, + "female vocalists": 55.0, + "Love": 2.0, + "death metal": 2.0, + "pop": 100.0, + "rock": 8.0, + "electronic": 5.0, + "seen live": 3.0, + "Hip-Hop": 4.0, + "alternative": 2.0, + "female vocalist": 4.0, + "00s": 2.0 + } + }, + { + "description": "Mark Ronson", + "features": { + "singer-songwriter": 1.0, + "jazz": 7.0, + "british": 68.0, + "experimental": 2.0, + "american": 1.0, + "cover": 4.0, + "electronica": 2.0, + "dance": 5.0, + "rap": 4.0, + "rnb": 2.0, + "House": 1.0, + "indie": 3.0, + "soul": 17.0, + "chillout": 2.0, + "pop": 68.0, + "rock": 3.0, + "electro": 2.0, + "hip hop": 11.0, + "electronic": 9.0, + "trip-hop": 1.0, + "britpop": 4.0, + "seen live": 31.0, + "Hip-Hop": 57.0, + "alternative": 9.0, + "00s": 7.0, + "funk": 100.0 + } + }, + { + "description": "Imagine Dragons", + "features": { + "indie": 100.0, + "male vocalists": 6.0, + "american": 12.0, + "rock": 64.0, + "pop": 10.0, + "indie rock": 96.0, + "electronic": 4.0, + "new wave": 2.0, + "indie pop": 57.0, + "seen live": 29.0, + "alternative": 86.0, + "alternative rock": 21.0, + "folk": 2.0 + } + }, + { + "description": "The Weeknd", + "features": { + "indie": 6.0, + "male vocalists": 5.0, + "soul": 9.0, + "chillout": 2.0, + "Mellow": 2.0, + "experimental": 6.0, + "ambient": 3.0, + "pop": 10.0, + "electronica": 5.0, + "electro": 1.0, + "downtempo": 7.0, + "hip hop": 2.0, + "electronic": 78.0, + "trip-hop": 3.0, + "rap": 2.0, + "indie pop": 2.0, + "seen live": 16.0, + "Hip-Hop": 8.0, + "rnb": 100.0, + "alternative": 5.0 + } + }, + { + "description": "Eminem", + "features": { + "male vocalists": 3.0, + "singer-songwriter": 2.0, + "emo": 1.0, + "american": 6.0, + "pop": 16.0, + "rock": 3.0, + "hip hop": 33.0, + "dance": 1.0, + "rap": 100.0, + "seen live": 4.0, + "Hip-Hop": 79.0, + "alternative": 3.0, + "00s": 2.0, + "90s": 2.0 + } + }, + { + "description": "Nirvana", + "features": { + "indie": 4.0, + "favorites": 1.0, + "male vocalists": 2.0, + "Grunge": 100.0, + "alternative": 35.0, + "metal": 3.0, + "american": 4.0, + "classic rock": 2.0, + "rock": 52.0, + "alternative rock": 34.0, + "punk rock": 3.0, + "punk": 7.0, + "indie rock": 2.0, + "hard rock": 3.0, + "90s": 28.0 + } + }, + { + "description": "Fleetwood Mac", + "features": { + "favorites": 2.0, + "male vocalists": 2.0, + "singer-songwriter": 2.0, + "british": 10.0, + "american": 3.0, + "classic rock": 100.0, + "blues": 41.0, + "rock": 67.0, + "pop": 33.0, + "oldies": 3.0, + "seen live": 7.0, + "alternative": 1.0, + "acoustic": 1.0, + "female vocalist": 2.0, + "guitar": 1.0, + "Progressive rock": 2.0, + "female vocalists": 10.0, + "folk": 2.0, + "60s": 5.0, + "70s": 48.0, + "80s": 12.0, + "90s": 2.0 + } + }, + { + "description": "Childish Gambino", + "features": { + "indie": 35.0, + "male vocalists": 1.0, + "soul": 5.0, + "american": 8.0, + "experimental": 3.0, + "pop": 3.0, + "psychedelic": 2.0, + "hip hop": 31.0, + "electronic": 2.0, + "dance": 1.0, + "rap": 70.0, + "seen live": 37.0, + "Hip-Hop": 100.0, + "rnb": 6.0, + "alternative": 4.0, + "funk": 8.0 + } + }, + { + "description": "Pink Floyd", + "features": { + "favorites": 1.0, + "indie": 1.0, + "Psychedelic Rock": 72.0, + "british": 13.0, + "experimental": 6.0, + "ambient": 1.0, + "classic rock": 79.0, + "rock": 53.0, + "psychedelic": 44.0, + "oldies": 1.0, + "seen live": 3.0, + "alternative": 6.0, + "Progressive rock": 100.0, + "alternative rock": 2.0, + "hard rock": 2.0, + "60s": 5.0, + "70s": 8.0, + "80s": 3.0 + } + }, + { + "description": "The Killers", + "features": { + "indie": 100.0, + "favorites": 2.0, + "male vocalists": 3.0, + "emo": 2.0, + "american": 9.0, + "british": 2.0, + "classic rock": 1.0, + "rock": 93.0, + "pop": 10.0, + "electronica": 1.0, + "punk rock": 1.0, + "indie rock": 90.0, + "new wave": 4.0, + "electronic": 3.0, + "dance": 1.0, + "britpop": 4.0, + "indie pop": 4.0, + "seen live": 39.0, + "alternative": 75.0, + "post-punk": 3.0, + "alternative rock": 64.0, + "00s": 3.0, + "punk": 4.0 + } + }, + { + "description": "The Rolling Stones", + "features": { + "indie": 2.0, + "male vocalists": 1.0, + "Psychedelic Rock": 2.0, + "british": 43.0, + "classic rock": 100.0, + "blues": 31.0, + "rock": 80.0, + "psychedelic": 3.0, + "pop": 2.0, + "oldies": 3.0, + "seen live": 14.0, + "alternative": 4.0, + "guitar": 1.0, + "hard rock": 8.0, + "60s": 41.0, + "70s": 8.0, + "80s": 4.0, + "90s": 2.0 + } + }, + { + "description": "Gorillaz", + "features": { + "indie": 46.0, + "favorites": 1.0, + "chillout": 3.0, + "british": 12.0, + "experimental": 6.0, + "rock": 58.0, + "electronica": 13.0, + "pop": 13.0, + "electro": 2.0, + "downtempo": 1.0, + "punk rock": 1.0, + "indie rock": 4.0, + "hip hop": 7.0, + "electronic": 88.0, + "trip-hop": 20.0, + "dance": 4.0, + "britpop": 4.0, + "rap": 4.0, + "indie pop": 2.0, + "seen live": 5.0, + "Hip-Hop": 63.0, + "alternative": 100.0, + "metal": 2.0, + "alternative rock": 11.0, + "00s": 3.0, + "punk": 2.0, + "funk": 3.0 + } + }, + { + "description": "Frank Ocean", + "features": { + "indie": 3.0, + "male vocalists": 6.0, + "singer-songwriter": 4.0, + "soul": 93.0, + "american": 8.0, + "experimental": 2.0, + "pop": 6.0, + "electronica": 1.0, + "hip hop": 8.0, + "electronic": 3.0, + "rap": 4.0, + "seen live": 14.0, + "Hip-Hop": 82.0, + "rnb": 100.0, + "alternative": 3.0, + "funk": 2.0 + } + }, + { + "description": "Panic! at the Disco", + "features": { + "male vocalists": 3.0, + "american": 8.0, + "electronica": 2.0, + "punk rock": 6.0, + "indie rock": 21.0, + "dance": 20.0, + "metal": 2.0, + "screamo": 2.0, + "punk": 21.0, + "Love": 2.0, + "indie": 68.0, + "favorites": 4.0, + "emo": 97.0, + "rock": 100.0, + "pop": 9.0, + "electronic": 4.0, + "indie pop": 2.0, + "seen live": 51.0, + "hardcore": 2.0, + "techno": 1.0, + "alternative": 99.0, + "alternative rock": 21.0, + "00s": 2.0 + } + }, + { + "description": "The Cure", + "features": { + "indie": 14.0, + "favorites": 2.0, + "male vocalists": 2.0, + "emo": 1.0, + "british": 16.0, + "classic rock": 3.0, + "rock": 52.0, + "pop": 6.0, + "psychedelic": 1.0, + "synthpop": 1.0, + "punk rock": 1.0, + "indie rock": 3.0, + "new wave": 100.0, + "electronic": 2.0, + "britpop": 2.0, + "indie pop": 1.0, + "seen live": 24.0, + "alternative": 68.0, + "Gothic": 10.0, + "post-punk": 100.0, + "alternative rock": 23.0, + "punk": 4.0, + "Love": 2.0, + "70s": 2.0, + "80s": 65.0, + "90s": 3.0 + } + }, + { + "description": "Madonna", + "features": { + "singer-songwriter": 3.0, + "american": 8.0, + "pop": 100.0, + "rock": 5.0, + "electronica": 3.0, + "electronic": 29.0, + "dance": 50.0, + "seen live": 9.0, + "rnb": 1.0, + "alternative": 2.0, + "female vocalist": 4.0, + "00s": 4.0, + "female vocalists": 50.0, + "Love": 1.0, + "80s": 35.0, + "90s": 8.0 + } + }, + { + "description": "The Chainsmokers", + "features": { + "male vocalists": 6.0, + "american": 28.0, + "electronica": 3.0, + "dance": 33.0, + "House": 63.0, + "pop": 79.0, + "electro": 6.0, + "electronic": 100.0, + "seen live": 17.0, + "techno": 3.0, + "alternative": 3.0 + } + }, + { + "description": "Linkin Park", + "features": { + "favorites": 2.0, + "indie": 1.0, + "male vocalists": 3.0, + "emo": 3.0, + "american": 6.0, + "rock": 100.0, + "pop": 3.0, + "punk rock": 2.0, + "electronic": 4.0, + "rap": 3.0, + "seen live": 21.0, + "Hip-Hop": 2.0, + "alternative": 67.0, + "metal": 37.0, + "Nu Metal": 92.0, + "alternative rock": 74.0, + "00s": 2.0, + "punk": 4.0, + "hard rock": 5.0 + } + }, + { + "description": "Lorde", + "features": { + "indie": 72.0, + "singer-songwriter": 7.0, + "soul": 1.0, + "experimental": 2.0, + "pop": 100.0, + "electronica": 5.0, + "rock": 1.0, + "synthpop": 7.0, + "electronic": 81.0, + "trip-hop": 2.0, + "indie pop": 92.0, + "seen live": 20.0, + "alternative": 17.0, + "female vocalist": 4.0, + "female vocalists": 28.0, + "folk": 2.0 + } + }, + { + "description": "The Black Keys", + "features": { + "indie": 50.0, + "male vocalists": 2.0, + "soul": 2.0, + "Psychedelic Rock": 2.0, + "american": 8.0, + "blues": 68.0, + "classic rock": 2.0, + "rock": 63.0, + "psychedelic": 2.0, + "indie rock": 48.0, + "seen live": 37.0, + "alternative": 11.0, + "alternative rock": 7.0, + "00s": 3.0, + "hard rock": 2.0 + } + }, + { + "description": "Daft Punk", + "features": { + "indie": 2.0, + "chillout": 2.0, + "experimental": 2.0, + "electronica": 37.0, + "rock": 3.0, + "pop": 2.0, + "electro": 11.0, + "synthpop": 1.0, + "electronic": 100.0, + "dance": 54.0, + "french": 19.0, + "seen live": 7.0, + "techno": 36.0, + "alternative": 6.0, + "House": 54.0, + "trance": 2.0, + "00s": 2.0, + "Soundtrack": 2.0, + "funk": 4.0, + "90s": 3.0 + } + }, + { + "description": "Led Zeppelin", + "features": { + "favorites": 1.0, + "indie": 1.0, + "Psychedelic Rock": 3.0, + "british": 9.0, + "classic rock": 100.0, + "blues": 9.0, + "rock": 64.0, + "psychedelic": 4.0, + "heavy metal": 8.0, + "oldies": 1.0, + "metal": 4.0, + "alternative": 4.0, + "guitar": 2.0, + "Progressive rock": 33.0, + "alternative rock": 1.0, + "hard rock": 63.0, + "folk": 1.0, + "60s": 3.0, + "70s": 36.0, + "80s": 1.0 + } + }, + { + "description": "Marshmello", + "features": { + "emo": 10.0, + "pop": 28.0, + "electronic": 91.0, + "dance": 100.0, + "rap": 28.0, + "seen live": 19.0, + "rnb": 19.0, + "House": 10.0 + } + }, + { + "description": "Khalid", + "features": { + "indie": 7.0, + "male vocalists": 7.0, + "soul": 88.0, + "american": 19.0, + "pop": 100.0, + "synthpop": 7.0, + "electronic": 13.0, + "seen live": 25.0, + "rnb": 75.0, + "alternative": 63.0, + "folk": 7.0 + } + }, + { + "description": "David Guetta", + "features": { + "chillout": 1.0, + "electronica": 7.0, + "pop": 6.0, + "electro": 6.0, + "electronic": 76.0, + "dance": 82.0, + "french": 11.0, + "seen live": 9.0, + "techno": 39.0, + "House": 100.0, + "trance": 4.0, + "00s": 2.0 + } + }, + { + "description": "Billie Eilish", + "features": { + "singer-songwriter": 3.0, + "american": 23.0, + "british": 3.0, + "experimental": 3.0, + "rnb": 3.0, + "female vocalists": 49.0, + "indie": 58.0, + "chillout": 12.0, + "ambient": 3.0, + "pop": 100.0, + "synthpop": 3.0, + "electronic": 58.0, + "indie pop": 83.0, + "seen live": 18.0, + "alternative": 40.0, + "female vocalist": 9.0 + } + }, + { + "description": "Ed Sheeran", + "features": { + "indie": 12.0, + "singer-songwriter": 78.0, + "male vocalists": 13.0, + "soul": 3.0, + "chillout": 2.0, + "Mellow": 2.0, + "british": 82.0, + "pop": 23.0, + "rock": 4.0, + "rap": 2.0, + "indie pop": 3.0, + "seen live": 21.0, + "Hip-Hop": 4.0, + "acoustic": 100.0, + "alternative": 8.0, + "guitar": 2.0, + "00s": 2.0, + "Soundtrack": 1.0, + "Love": 2.0, + "folk": 12.0 + } + }, + { + "description": "Lil Nas X", + "features": { + "country": 54.0, + "male vocalists": 8.0, + "american": 24.0, + "rock": 16.0, + "pop": 8.0, + "hip hop": 47.0, + "rap": 62.0, + "Hip-Hop": 100.0, + "alternative": 24.0 + } + }, + { + "description": "Queen", + "features": { + "favorites": 2.0, + "male vocalists": 3.0, + "british": 17.0, + "classic rock": 100.0, + "rock": 81.0, + "pop": 7.0, + "heavy metal": 2.0, + "oldies": 2.0, + "seen live": 2.0, + "alternative": 4.0, + "metal": 2.0, + "Progressive rock": 6.0, + "alternative rock": 1.0, + "hard rock": 38.0, + "70s": 8.0, + "80s": 41.0, + "90s": 2.0 + } + }, + { + "description": "Ariana Grande", + "features": { + "american": 13.0, + "beautiful": 2.0, + "dance": 11.0, + "House": 2.0, + "Soundtrack": 3.0, + "chillout": 3.0, + "pop": 100.0, + "rock": 2.0, + "electronic": 3.0, + "seen live": 3.0, + "female vocalist": 3.0, + "singer-songwriter": 5.0, + "rnb": 51.0, + "female vocalists": 54.0, + "soul": 10.0, + "synthpop": 2.0, + "hip hop": 3.0, + "Hip-Hop": 3.0 + } + }, + { + "description": "Post Malone", + "features": { + "american": 8.0, + "rap": 72.0, + "rnb": 14.0, + "pop": 8.0, + "synthpop": 3.0, + "hip hop": 35.0, + "seen live": 19.0, + "Hip-Hop": 100.0 + } + }, + { + "description": "Drake", + "features": { + "indie": 4.0, + "male vocalists": 5.0, + "soul": 2.0, + "pop": 6.0, + "hip hop": 37.0, + "rap": 72.0, + "seen live": 8.0, + "Hip-Hop": 100.0, + "rnb": 50.0, + "00s": 1.0 + } + }, + { + "description": "Kanye West", + "features": { + "indie": 2.0, + "male vocalists": 3.0, + "soul": 4.0, + "american": 6.0, + "experimental": 2.0, + "pop": 6.0, + "rock": 2.0, + "electronica": 1.0, + "hip hop": 40.0, + "electronic": 3.0, + "dance": 2.0, + "rap": 69.0, + "seen live": 22.0, + "Hip-Hop": 100.0, + "rnb": 31.0, + "alternative": 3.0, + "00s": 3.0 + } + }, + { + "description": "The Beatles", + "features": { + "indie": 3.0, + "favorites": 2.0, + "male vocalists": 2.0, + "singer-songwriter": 2.0, + "Psychedelic Rock": 6.0, + "british": 58.0, + "experimental": 2.0, + "classic rock": 100.0, + "rock": 71.0, + "pop": 41.0, + "psychedelic": 13.0, + "indie rock": 2.0, + "oldies": 10.0, + "britpop": 4.0, + "alternative": 4.0, + "Progressive rock": 2.0, + "alternative rock": 1.0, + "Love": 2.0, + "folk": 1.0, + "60s": 56.0, + "70s": 3.0 + } + }, + { + "description": "Taylor Swift", + "features": { + "country": 100.0, + "singer-songwriter": 42.0, + "chillout": 2.0, + "american": 8.0, + "pop": 71.0, + "rock": 2.0, + "beautiful": 2.0, + "synthpop": 2.0, + "seen live": 4.0, + "acoustic": 31.0, + "female vocalist": 3.0, + "00s": 3.0, + "female vocalists": 60.0, + "Love": 3.0, + "folk": 2.0 + } + }, + { + "description": "Radiohead", + "features": { + "indie": 59.0, + "favorites": 2.0, + "male vocalists": 2.0, + "emo": 1.0, + "chillout": 2.0, + "british": 22.0, + "experimental": 14.0, + "ambient": 2.0, + "classic rock": 1.0, + "rock": 73.0, + "electronica": 5.0, + "psychedelic": 3.0, + "pop": 2.0, + "beautiful": 1.0, + "post-rock": 3.0, + "indie rock": 15.0, + "electronic": 48.0, + "trip-hop": 1.0, + "britpop": 17.0, + "indie pop": 1.0, + "seen live": 27.0, + "alternative": 100.0, + "Progressive rock": 6.0, + "alternative rock": 81.0, + "00s": 2.0, + "idm": 1.0, + "90s": 5.0 + } + }, + { + "description": "Beyoncé", + "features": { + "singer-songwriter": 3.0, + "jazz": 2.0, + "american": 9.0, + "beautiful": 2.0, + "dance": 13.0, + "rap": 2.0, + "rnb": 100.0, + "female vocalists": 70.0, + "Love": 3.0, + "soul": 56.0, + "pop": 91.0, + "hip hop": 5.0, + "electronic": 2.0, + "seen live": 6.0, + "Hip-Hop": 45.0, + "alternative": 1.0, + "female vocalist": 4.0, + "00s": 3.0, + "funk": 2.0 + } + }, + { + "description": "Shawn Mendes", + "features": { + "male vocalists": 28.0, + "singer-songwriter": 13.0, + "british": 7.0, + "rnb": 7.0, + "acoustic": 37.0, + "metal": 4.0, + "folk": 37.0, + "indie": 10.0, + "soul": 4.0, + "pop": 100.0, + "seen live": 13.0, + "alternative": 4.0, + "alternative rock": 4.0 + } + }, + { + "description": "David Bowie", + "features": { + "singer-songwriter": 11.0, + "male vocalists": 4.0, + "Psychedelic Rock": 2.0, + "british": 23.0, + "experimental": 9.0, + "classic rock": 86.0, + "psychedelic": 3.0, + "electronica": 2.0, + "indie rock": 2.0, + "dance": 2.0, + "post-punk": 2.0, + "punk": 2.0, + "hard rock": 3.0, + "folk": 2.0, + "70s": 15.0, + "90s": 4.0, + "indie": 6.0, + "favorites": 2.0, + "soul": 2.0, + "ambient": 2.0, + "rock": 100.0, + "pop": 19.0, + "new wave": 7.0, + "electronic": 4.0, + "oldies": 2.0, + "britpop": 2.0, + "seen live": 11.0, + "alternative": 43.0, + "Progressive rock": 4.0, + "alternative rock": 6.0, + "industrial": 2.0, + "00s": 2.0, + "60s": 3.0, + "80s": 48.0, + "funk": 2.0 + } + }, + { + "description": "Kendrick Lamar", + "features": { + "male vocalists": 2.0, + "american": 7.0, + "hip hop": 17.0, + "rap": 70.0, + "seen live": 38.0, + "Hip-Hop": 100.0, + "funk": 2.0 + } + }, + { + "description": "Rihanna", + "features": { + "american": 4.0, + "beautiful": 2.0, + "dance": 56.0, + "rap": 2.0, + "rnb": 89.0, + "House": 1.0, + "female vocalists": 63.0, + "Love": 2.0, + "soul": 4.0, + "pop": 100.0, + "rock": 2.0, + "hip hop": 6.0, + "electronic": 3.0, + "seen live": 7.0, + "Hip-Hop": 41.0, + "alternative": 1.0, + "female vocalist": 4.0, + "00s": 3.0, + "reggae": 6.0 + } + }, + { + "description": "Arctic Monkeys", + "features": { + "indie": 98.0, + "male vocalists": 2.0, + "Psychedelic Rock": 1.0, + "british": 75.0, + "rock": 63.0, + "pop": 2.0, + "punk rock": 1.0, + "indie rock": 100.0, + "britpop": 16.0, + "indie pop": 2.0, + "seen live": 45.0, + "alternative": 60.0, + "post-punk": 3.0, + "alternative rock": 19.0, + "00s": 3.0, + "punk": 2.0 + } + }, + { + "description": "Tyler, the Creator", + "features": { + "experimental": 4.0, + "american": 4.0, + "hip hop": 12.0, + "rap": 55.0, + "seen live": 15.0, + "Hip-Hop": 100.0, + "alternative": 2.0 + } + }, + { + "description": "Lana Del Rey", + "features": { + "singer-songwriter": 10.0, + "jazz": 2.0, + "american": 14.0, + "beautiful": 2.0, + "indie rock": 2.0, + "female vocalists": 100.0, + "folk": 2.0, + "indie": 93.0, + "soul": 3.0, + "chillout": 3.0, + "blues": 2.0, + "pop": 80.0, + "rock": 2.0, + "electronic": 2.0, + "trip-hop": 10.0, + "indie pop": 88.0, + "seen live": 12.0, + "alternative": 67.0, + "female vocalist": 4.0, + "alternative rock": 2.0 + } + }, + { + "description": "Katy Perry", + "features": { + "indie": 25.0, + "singer-songwriter": 4.0, + "american": 9.0, + "pop": 100.0, + "rock": 24.0, + "beautiful": 2.0, + "indie rock": 1.0, + "electronic": 4.0, + "dance": 9.0, + "indie pop": 1.0, + "seen live": 7.0, + "rnb": 1.0, + "alternative": 3.0, + "female vocalist": 5.0, + "00s": 3.0, + "female vocalists": 62.0, + "Love": 2.0 + } + }, + { + "description": "Lady Gaga", + "features": { + "singer-songwriter": 4.0, + "american": 7.0, + "pop": 100.0, + "electronica": 3.0, + "rock": 2.0, + "electro": 2.0, + "synthpop": 2.0, + "electronic": 53.0, + "dance": 73.0, + "seen live": 8.0, + "techno": 1.0, + "rnb": 1.0, + "alternative": 1.0, + "female vocalist": 21.0, + "00s": 3.0, + "female vocalists": 43.0, + "Love": 2.0 + } + }, + { + "description": "Tame Impala", + "features": { + "indie": 21.0, + "male vocalists": 1.0, + "Psychedelic Rock": 100.0, + "experimental": 4.0, + "psychedelic": 92.0, + "rock": 41.0, + "indie rock": 60.0, + "electronic": 3.0, + "indie pop": 2.0, + "seen live": 53.0, + "alternative": 7.0, + "Progressive rock": 3.0, + "alternative rock": 3.0, + "00s": 3.0 + } + }, + { + "description": "Calvin Harris", + "features": { + "indie": 8.0, + "male vocalists": 3.0, + "singer-songwriter": 1.0, + "british": 15.0, + "electronica": 34.0, + "pop": 8.0, + "electro": 53.0, + "synthpop": 4.0, + "electronic": 100.0, + "dance": 75.0, + "indie pop": 2.0, + "seen live": 36.0, + "techno": 1.0, + "alternative": 1.0, + "House": 9.0, + "00s": 3.0, + "80s": 1.0 + } + }, + { + "description": "Red Hot Chili Peppers", + "features": { + "indie": 5.0, + "favorites": 1.0, + "male vocalists": 1.0, + "american": 5.0, + "classic rock": 3.0, + "rock": 100.0, + "pop": 2.0, + "punk rock": 2.0, + "indie rock": 2.0, + "seen live": 23.0, + "Grunge": 2.0, + "alternative": 54.0, + "metal": 2.0, + "alternative rock": 59.0, + "00s": 2.0, + "punk": 6.0, + "hard rock": 2.0, + "80s": 2.0, + "funk": 50.0, + "90s": 5.0 + } + }, + { + "description": "Coldplay", + "features": { + "indie": 55.0, + "favorites": 2.0, + "male vocalists": 3.0, + "singer-songwriter": 2.0, + "emo": 2.0, + "Mellow": 3.0, + "chillout": 3.0, + "british": 28.0, + "classic rock": 2.0, + "rock": 100.0, + "pop": 18.0, + "piano": 3.0, + "indie rock": 11.0, + "electronic": 2.0, + "britpop": 81.0, + "indie pop": 2.0, + "seen live": 22.0, + "alternative": 87.0, + "acoustic": 2.0, + "alternative rock": 68.0, + "00s": 2.0, + "Love": 2.0, + "90s": 2.0 + } + }, + { + "description": "Miley Cyrus", + "features": { + "country": 3.0, + "singer-songwriter": 1.0, + "american": 12.0, + "beautiful": 2.0, + "grindcore": 1.0, + "dance": 42.0, + "rnb": 4.0, + "Soundtrack": 2.0, + "female vocalists": 55.0, + "Love": 2.0, + "death metal": 2.0, + "pop": 100.0, + "rock": 8.0, + "electronic": 5.0, + "seen live": 3.0, + "Hip-Hop": 4.0, + "alternative": 2.0, + "female vocalist": 4.0, + "00s": 2.0 + } + }, + { + "description": "Mark Ronson", + "features": { + "singer-songwriter": 1.0, + "jazz": 7.0, + "british": 68.0, + "experimental": 2.0, + "american": 1.0, + "cover": 4.0, + "electronica": 2.0, + "dance": 5.0, + "rap": 4.0, + "rnb": 2.0, + "House": 1.0, + "indie": 3.0, + "soul": 17.0, + "chillout": 2.0, + "pop": 68.0, + "rock": 3.0, + "electro": 2.0, + "hip hop": 11.0, + "electronic": 9.0, + "trip-hop": 1.0, + "britpop": 4.0, + "seen live": 31.0, + "Hip-Hop": 57.0, + "alternative": 9.0, + "00s": 7.0, + "funk": 100.0 + } + }, + { + "description": "Imagine Dragons", + "features": { + "indie": 100.0, + "male vocalists": 6.0, + "american": 12.0, + "rock": 64.0, + "pop": 10.0, + "indie rock": 96.0, + "electronic": 4.0, + "new wave": 2.0, + "indie pop": 57.0, + "seen live": 29.0, + "alternative": 86.0, + "alternative rock": 21.0, + "folk": 2.0 + } + }, + { + "description": "The Weeknd", + "features": { + "indie": 6.0, + "male vocalists": 5.0, + "soul": 9.0, + "chillout": 2.0, + "Mellow": 2.0, + "experimental": 6.0, + "ambient": 3.0, + "pop": 10.0, + "electronica": 5.0, + "electro": 1.0, + "downtempo": 7.0, + "hip hop": 2.0, + "electronic": 78.0, + "trip-hop": 3.0, + "rap": 2.0, + "indie pop": 2.0, + "seen live": 16.0, + "Hip-Hop": 8.0, + "rnb": 100.0, + "alternative": 5.0 + } + }, + { + "description": "Eminem", + "features": { + "male vocalists": 3.0, + "singer-songwriter": 2.0, + "emo": 1.0, + "american": 6.0, + "pop": 16.0, + "rock": 3.0, + "hip hop": 33.0, + "dance": 1.0, + "rap": 100.0, + "seen live": 4.0, + "Hip-Hop": 79.0, + "alternative": 3.0, + "00s": 2.0, + "90s": 2.0 + } + }, + { + "description": "Nirvana", + "features": { + "indie": 4.0, + "favorites": 1.0, + "male vocalists": 2.0, + "Grunge": 100.0, + "alternative": 35.0, + "metal": 3.0, + "american": 4.0, + "classic rock": 2.0, + "rock": 52.0, + "alternative rock": 34.0, + "punk rock": 3.0, + "punk": 7.0, + "indie rock": 2.0, + "hard rock": 3.0, + "90s": 28.0 + } + }, + { + "description": "Fleetwood Mac", + "features": { + "favorites": 2.0, + "male vocalists": 2.0, + "singer-songwriter": 2.0, + "british": 10.0, + "american": 3.0, + "classic rock": 100.0, + "blues": 41.0, + "rock": 67.0, + "pop": 33.0, + "oldies": 3.0, + "seen live": 7.0, + "alternative": 1.0, + "acoustic": 1.0, + "female vocalist": 2.0, + "guitar": 1.0, + "Progressive rock": 2.0, + "female vocalists": 10.0, + "folk": 2.0, + "60s": 5.0, + "70s": 48.0, + "80s": 12.0, + "90s": 2.0 + } + }, + { + "description": "Childish Gambino", + "features": { + "indie": 35.0, + "male vocalists": 1.0, + "soul": 5.0, + "american": 8.0, + "experimental": 3.0, + "pop": 3.0, + "psychedelic": 2.0, + "hip hop": 31.0, + "electronic": 2.0, + "dance": 1.0, + "rap": 70.0, + "seen live": 37.0, + "Hip-Hop": 100.0, + "rnb": 6.0, + "alternative": 4.0, + "funk": 8.0 + } + }, + { + "description": "Pink Floyd", + "features": { + "favorites": 1.0, + "indie": 1.0, + "Psychedelic Rock": 72.0, + "british": 13.0, + "experimental": 6.0, + "ambient": 1.0, + "classic rock": 79.0, + "rock": 53.0, + "psychedelic": 44.0, + "oldies": 1.0, + "seen live": 3.0, + "alternative": 6.0, + "Progressive rock": 100.0, + "alternative rock": 2.0, + "hard rock": 2.0, + "60s": 5.0, + "70s": 8.0, + "80s": 3.0 + } + }, + { + "description": "The Killers", + "features": { + "indie": 100.0, + "favorites": 2.0, + "male vocalists": 3.0, + "emo": 2.0, + "american": 9.0, + "british": 2.0, + "classic rock": 1.0, + "rock": 93.0, + "pop": 10.0, + "electronica": 1.0, + "punk rock": 1.0, + "indie rock": 90.0, + "new wave": 4.0, + "electronic": 3.0, + "dance": 1.0, + "britpop": 4.0, + "indie pop": 4.0, + "seen live": 39.0, + "alternative": 75.0, + "post-punk": 3.0, + "alternative rock": 64.0, + "00s": 3.0, + "punk": 4.0 + } + }, + { + "description": "The Rolling Stones", + "features": { + "indie": 2.0, + "male vocalists": 1.0, + "Psychedelic Rock": 2.0, + "british": 43.0, + "classic rock": 100.0, + "blues": 31.0, + "rock": 80.0, + "psychedelic": 3.0, + "pop": 2.0, + "oldies": 3.0, + "seen live": 14.0, + "alternative": 4.0, + "guitar": 1.0, + "hard rock": 8.0, + "60s": 41.0, + "70s": 8.0, + "80s": 4.0, + "90s": 2.0 + } + }, + { + "description": "Gorillaz", + "features": { + "indie": 46.0, + "favorites": 1.0, + "chillout": 3.0, + "british": 12.0, + "experimental": 6.0, + "rock": 58.0, + "electronica": 13.0, + "pop": 13.0, + "electro": 2.0, + "downtempo": 1.0, + "punk rock": 1.0, + "indie rock": 4.0, + "hip hop": 7.0, + "electronic": 88.0, + "trip-hop": 20.0, + "dance": 4.0, + "britpop": 4.0, + "rap": 4.0, + "indie pop": 2.0, + "seen live": 5.0, + "Hip-Hop": 63.0, + "alternative": 100.0, + "metal": 2.0, + "alternative rock": 11.0, + "00s": 3.0, + "punk": 2.0, + "funk": 3.0 + } + }, + { + "description": "Frank Ocean", + "features": { + "indie": 3.0, + "male vocalists": 6.0, + "singer-songwriter": 4.0, + "soul": 93.0, + "american": 8.0, + "experimental": 2.0, + "pop": 6.0, + "electronica": 1.0, + "hip hop": 8.0, + "electronic": 3.0, + "rap": 4.0, + "seen live": 14.0, + "Hip-Hop": 82.0, + "rnb": 100.0, + "alternative": 3.0, + "funk": 2.0 + } + }, + { + "description": "Panic! at the Disco", + "features": { + "male vocalists": 3.0, + "american": 8.0, + "electronica": 2.0, + "punk rock": 6.0, + "indie rock": 21.0, + "dance": 20.0, + "metal": 2.0, + "screamo": 2.0, + "punk": 21.0, + "Love": 2.0, + "indie": 68.0, + "favorites": 4.0, + "emo": 97.0, + "rock": 100.0, + "pop": 9.0, + "electronic": 4.0, + "indie pop": 2.0, + "seen live": 51.0, + "hardcore": 2.0, + "techno": 1.0, + "alternative": 99.0, + "alternative rock": 21.0, + "00s": 2.0 + } + }, + { + "description": "The Cure", + "features": { + "indie": 14.0, + "favorites": 2.0, + "male vocalists": 2.0, + "emo": 1.0, + "british": 16.0, + "classic rock": 3.0, + "rock": 52.0, + "pop": 6.0, + "psychedelic": 1.0, + "synthpop": 1.0, + "punk rock": 1.0, + "indie rock": 3.0, + "new wave": 100.0, + "electronic": 2.0, + "britpop": 2.0, + "indie pop": 1.0, + "seen live": 24.0, + "alternative": 68.0, + "Gothic": 10.0, + "post-punk": 100.0, + "alternative rock": 23.0, + "punk": 4.0, + "Love": 2.0, + "70s": 2.0, + "80s": 65.0, + "90s": 3.0 + } + }, + { + "description": "Madonna", + "features": { + "singer-songwriter": 3.0, + "american": 8.0, + "pop": 100.0, + "rock": 5.0, + "electronica": 3.0, + "electronic": 29.0, + "dance": 50.0, + "seen live": 9.0, + "rnb": 1.0, + "alternative": 2.0, + "female vocalist": 4.0, + "00s": 4.0, + "female vocalists": 50.0, + "Love": 1.0, + "80s": 35.0, + "90s": 8.0 + } + }, + { + "description": "The Chainsmokers", + "features": { + "male vocalists": 6.0, + "american": 28.0, + "electronica": 3.0, + "dance": 33.0, + "House": 63.0, + "pop": 79.0, + "electro": 6.0, + "electronic": 100.0, + "seen live": 17.0, + "techno": 3.0, + "alternative": 3.0 + } + }, + { + "description": "Linkin Park", + "features": { + "favorites": 2.0, + "indie": 1.0, + "male vocalists": 3.0, + "emo": 3.0, + "american": 6.0, + "rock": 100.0, + "pop": 3.0, + "punk rock": 2.0, + "electronic": 4.0, + "rap": 3.0, + "seen live": 21.0, + "Hip-Hop": 2.0, + "alternative": 67.0, + "metal": 37.0, + "Nu Metal": 92.0, + "alternative rock": 74.0, + "00s": 2.0, + "punk": 4.0, + "hard rock": 5.0 + } + }, + { + "description": "Lorde", + "features": { + "indie": 72.0, + "singer-songwriter": 7.0, + "soul": 1.0, + "experimental": 2.0, + "pop": 100.0, + "electronica": 5.0, + "rock": 1.0, + "synthpop": 7.0, + "electronic": 81.0, + "trip-hop": 2.0, + "indie pop": 92.0, + "seen live": 20.0, + "alternative": 17.0, + "female vocalist": 4.0, + "female vocalists": 28.0, + "folk": 2.0 + } + }, + { + "description": "The Black Keys", + "features": { + "indie": 50.0, + "male vocalists": 2.0, + "soul": 2.0, + "Psychedelic Rock": 2.0, + "american": 8.0, + "blues": 68.0, + "classic rock": 2.0, + "rock": 63.0, + "psychedelic": 2.0, + "indie rock": 48.0, + "seen live": 37.0, + "alternative": 11.0, + "alternative rock": 7.0, + "00s": 3.0, + "hard rock": 2.0 + } + }, + { + "description": "Daft Punk", + "features": { + "indie": 2.0, + "chillout": 2.0, + "experimental": 2.0, + "electronica": 37.0, + "rock": 3.0, + "pop": 2.0, + "electro": 11.0, + "synthpop": 1.0, + "electronic": 100.0, + "dance": 54.0, + "french": 19.0, + "seen live": 7.0, + "techno": 36.0, + "alternative": 6.0, + "House": 54.0, + "trance": 2.0, + "00s": 2.0, + "Soundtrack": 2.0, + "funk": 4.0, + "90s": 3.0 + } + }, + { + "description": "Led Zeppelin", + "features": { + "favorites": 1.0, + "indie": 1.0, + "Psychedelic Rock": 3.0, + "british": 9.0, + "classic rock": 100.0, + "blues": 9.0, + "rock": 64.0, + "psychedelic": 4.0, + "heavy metal": 8.0, + "oldies": 1.0, + "metal": 4.0, + "alternative": 4.0, + "guitar": 2.0, + "Progressive rock": 33.0, + "alternative rock": 1.0, + "hard rock": 63.0, + "folk": 1.0, + "60s": 3.0, + "70s": 36.0, + "80s": 1.0 + } + }, + { + "description": "Marshmello", + "features": { + "emo": 10.0, + "pop": 28.0, + "electronic": 91.0, + "dance": 100.0, + "rap": 28.0, + "seen live": 19.0, + "rnb": 19.0, + "House": 10.0 + } + }, + { + "description": "Khalid", + "features": { + "indie": 7.0, + "male vocalists": 7.0, + "soul": 88.0, + "american": 19.0, + "pop": 100.0, + "synthpop": 7.0, + "electronic": 13.0, + "seen live": 25.0, + "rnb": 75.0, + "alternative": 63.0, + "folk": 7.0 + } + }, + { + "description": "David Guetta", + "features": { + "chillout": 1.0, + "electronica": 7.0, + "pop": 6.0, + "electro": 6.0, + "electronic": 76.0, + "dance": 82.0, + "french": 11.0, + "seen live": 9.0, + "techno": 39.0, + "House": 100.0, + "trance": 4.0, + "00s": 2.0 + } + }, + { + "description": "Lizzo", + "features": { + "american": 16.0, + "dance": 8.0, + "rap": 47.0, + "rnb": 54.0, + "female vocalists": 24.0, + "pop": 16.0, + "hip hop": 47.0, + "seen live": 93.0, + "Hip-Hop": 100.0, + "alternative": 8.0 + } + }, + { + "description": "Sia", + "features": { + "indie": 70.0, + "favorites": 1.0, + "singer-songwriter": 18.0, + "soul": 3.0, + "jazz": 2.0, + "chillout": 76.0, + "Mellow": 4.0, + "ambient": 2.0, + "pop": 20.0, + "electronica": 4.0, + "rock": 2.0, + "beautiful": 4.0, + "piano": 1.0, + "downtempo": 57.0, + "indie rock": 1.0, + "electronic": 16.0, + "trip-hop": 57.0, + "dance": 2.0, + "indie pop": 8.0, + "lounge": 2.0, + "seen live": 7.0, + "alternative": 9.0, + "acoustic": 2.0, + "female vocalist": 7.0, + "alternative rock": 1.0, + "00s": 2.0, + "female vocalists": 100.0, + "Love": 2.0 + } + }, + { + "description": "Elton John", + "features": { + "favorites": 2.0, + "indie": 1.0, + "singer-songwriter": 64.0, + "male vocalists": 9.0, + "soul": 2.0, + "british": 28.0, + "classic rock": 87.0, + "blues": 1.0, + "pop": 100.0, + "rock": 59.0, + "piano": 54.0, + "oldies": 4.0, + "britpop": 2.0, + "seen live": 19.0, + "alternative": 2.0, + "Soundtrack": 3.0, + "00s": 2.0, + "Love": 2.0, + "60s": 2.0, + "70s": 16.0, + "80s": 16.0, + "90s": 5.0 + } + }, + { + "description": "Twenty One Pilots", + "features": { + "american": 15.0, + "experimental": 6.0, + "electronica": 1.0, + "piano": 1.0, + "indie rock": 12.0, + "rap": 48.0, + "pop": 17.0, + "rock": 11.0, + "electronic": 100.0, + "noise": 1.0, + "indie pop": 71.0, + "seen live": 50.0, + "male vocalists": 4.0, + "punk": 1.0, + "Love": 1.0, + "indie": 84.0, + "emo": 5.0, + "electro": 1.0, + "synthpop": 1.0, + "hip hop": 7.0, + "Hip-Hop": 27.0, + "alternative": 84.0, + "alternative rock": 23.0 + } + }, + { + "description": "Avicii", + "features": { + "swedish": 20.0, + "seen live": 14.0, + "techno": 2.0, + "House": 100.0, + "trance": 3.0, + "electronica": 6.0, + "pop": 3.0, + "electro": 5.0, + "electronic": 83.0, + "dance": 66.0 + } + }, + { + "description": "Ellie Goulding", + "features": { + "indie": 85.0, + "singer-songwriter": 11.0, + "chillout": 3.0, + "british": 89.0, + "pop": 36.0, + "electronica": 3.0, + "rock": 1.0, + "electro": 2.0, + "synthpop": 9.0, + "electronic": 98.0, + "dance": 5.0, + "britpop": 2.0, + "indie pop": 78.0, + "seen live": 27.0, + "alternative": 6.0, + "acoustic": 2.0, + "female vocalist": 5.0, + "00s": 2.0, + "female vocalists": 100.0, + "folk": 4.0 + } + }, + { + "description": "Muse", + "features": { + "indie": 43.0, + "favorites": 2.0, + "male vocalists": 3.0, + "emo": 2.0, + "british": 21.0, + "experimental": 3.0, + "rock": 83.0, + "pop": 2.0, + "piano": 1.0, + "indie rock": 12.0, + "electronic": 4.0, + "britpop": 10.0, + "seen live": 46.0, + "alternative": 70.0, + "metal": 1.0, + "Progressive rock": 57.0, + "alternative rock": 100.0, + "00s": 2.0, + "hard rock": 2.0, + "90s": 2.0 + } + }, + { + "description": "Green Day", + "features": { + "indie": 3.0, + "favorites": 2.0, + "male vocalists": 2.0, + "emo": 4.0, + "american": 7.0, + "classic rock": 2.0, + "rock": 75.0, + "pop": 6.0, + "punk rock": 100.0, + "indie rock": 2.0, + "seen live": 26.0, + "Grunge": 1.0, + "alternative": 47.0, + "metal": 2.0, + "alternative rock": 17.0, + "00s": 2.0, + "punk": 69.0, + "hard rock": 2.0, + "90s": 5.0 + } + }, + { + "description": "Metallica", + "features": { + "seen live": 31.0, + "emo": 2.0, + "metal": 91.0, + "alternative": 2.0, + "american": 6.0, + "Power metal": 1.0, + "classic rock": 6.0, + "rock": 34.0, + "alternative rock": 2.0, + "hard rock": 41.0, + "heavy metal": 74.0, + "80s": 4.0, + "thrash metal": 100.0, + "90s": 3.0, + "Progressive metal": 2.0 + } + }, + { + "description": "Sam Smith", + "features": { + "indie": 3.0, + "male vocalists": 9.0, + "singer-songwriter": 8.0, + "soul": 100.0, + "british": 55.0, + "pop": 87.0, + "electronic": 45.0, + "dance": 32.0, + "seen live": 30.0, + "rnb": 7.0, + "alternative": 3.0, + "folk": 2.0 + } + }, + { + "description": "Kygo", + "features": { + "electronica": 7.0, + "downtempo": 49.0, + "dance": 5.0, + "House": 74.0, + "chillout": 65.0, + "pop": 5.0, + "synthpop": 3.0, + "electronic": 100.0, + "seen live": 36.0 + } + }, + { + "description": "Halsey", + "features": { + "american": 34.0, + "classic rock": 2.0, + "beautiful": 4.0, + "black metal": 4.0, + "pop": 100.0, + "electronic": 38.0, + "noise": 2.0, + "heavy metal": 2.0, + "indie pop": 44.0, + "seen live": 32.0, + "female vocalist": 4.0, + "singer-songwriter": 8.0, + "british": 4.0, + "rnb": 4.0, + "trance": 2.0, + "female vocalists": 44.0, + "indie": 68.0, + "soul": 4.0, + "emo": 4.0, + "synthpop": 12.0, + "alternative": 32.0 + } + }, + { + "description": "Michael Jackson", + "features": { + "male vocalists": 5.0, + "singer-songwriter": 2.0, + "soul": 36.0, + "american": 6.0, + "classic rock": 2.0, + "pop": 100.0, + "rock": 8.0, + "electronic": 1.0, + "dance": 41.0, + "seen live": 3.0, + "rnb": 7.0, + "70s": 3.0, + "80s": 50.0, + "funk": 34.0, + "90s": 5.0 + } + }, + { + "description": "Maroon 5", + "features": { + "indie": 12.0, + "favorites": 3.0, + "male vocalists": 9.0, + "singer-songwriter": 2.0, + "soul": 3.0, + "emo": 2.0, + "jazz": 2.0, + "american": 11.0, + "classic rock": 2.0, + "rock": 100.0, + "pop": 96.0, + "punk rock": 2.0, + "indie rock": 3.0, + "electronic": 1.0, + "dance": 2.0, + "britpop": 1.0, + "indie pop": 2.0, + "seen live": 16.0, + "alternative": 69.0, + "acoustic": 2.0, + "metal": 1.0, + "alternative rock": 52.0, + "00s": 4.0, + "punk": 3.0, + "Love": 2.0, + "funk": 5.0 + } + }, + { + "description": "Charli XCX", + "features": { + "singer-songwriter": 5.0, + "british": 26.0, + "experimental": 2.0, + "electronica": 5.0, + "dance": 4.0, + "female vocalists": 56.0, + "indie": 7.0, + "chillout": 11.0, + "pop": 98.0, + "rock": 2.0, + "electro": 4.0, + "synthpop": 100.0, + "electronic": 99.0, + "new wave": 4.0, + "britpop": 2.0, + "indie pop": 7.0, + "seen live": 63.0, + "Hip-Hop": 2.0, + "alternative": 5.0, + "female vocalist": 2.0, + "00s": 2.0 + } + }, + { + "description": "Foo Fighters", + "features": { + "indie": 7.0, + "favorites": 2.0, + "male vocalists": 2.0, + "emo": 1.0, + "american": 5.0, + "classic rock": 1.0, + "rock": 100.0, + "pop": 2.0, + "punk rock": 2.0, + "indie rock": 3.0, + "seen live": 35.0, + "Grunge": 48.0, + "alternative": 47.0, + "metal": 2.0, + "alternative rock": 69.0, + "00s": 2.0, + "punk": 5.0, + "hard rock": 30.0, + "90s": 4.0 + } + }, + { + "description": "Florence + the Machine", + "features": { + "indie": 100.0, + "singer-songwriter": 7.0, + "soul": 5.0, + "british": 72.0, + "experimental": 2.0, + "pop": 10.0, + "rock": 5.0, + "beautiful": 1.0, + "indie rock": 8.0, + "electronic": 2.0, + "britpop": 2.0, + "indie pop": 61.0, + "seen live": 29.0, + "alternative": 72.0, + "female vocalist": 3.0, + "alternative rock": 5.0, + "00s": 2.0, + "female vocalists": 86.0, + "Love": 2.0, + "folk": 6.0 + } + }, + { + "description": "blink-182", + "features": { + "indie": 3.0, + "favorites": 2.0, + "male vocalists": 2.0, + "emo": 8.0, + "american": 6.0, + "rock": 56.0, + "pop": 5.0, + "punk rock": 100.0, + "indie rock": 2.0, + "seen live": 23.0, + "hardcore": 2.0, + "alternative": 42.0, + "metal": 2.0, + "alternative rock": 10.0, + "00s": 1.0, + "punk": 72.0, + "hard rock": 1.0, + "90s": 4.0 + } + }, + { + "description": "Vampire Weekend", + "features": { + "indie": 100.0, + "male vocalists": 3.0, + "american": 11.0, + "experimental": 6.0, + "rock": 37.0, + "pop": 9.0, + "indie rock": 82.0, + "electronic": 2.0, + "indie pop": 63.0, + "seen live": 58.0, + "alternative": 57.0, + "alternative rock": 7.0, + "00s": 4.0, + "folk": 1.0 + } + }, + { + "description": "Martin Garrix", + "features": { + "german": 2.0, + "experimental": 2.0, + "american": 2.0, + "electronica": 5.0, + "dance": 57.0, + "House": 100.0, + "Soundtrack": 2.0, + "black metal": 2.0, + "Power metal": 2.0, + "pop": 30.0, + "electronic": 80.0, + "noise": 2.0, + "seen live": 38.0, + "Grunge": 2.0, + "techno": 2.0, + "trance": 3.0, + "electro": 5.0 + } + }, + { + "description": "Paramore", + "features": { + "indie": 9.0, + "favorites": 2.0, + "emo": 35.0, + "american": 7.0, + "rock": 100.0, + "pop": 6.0, + "punk rock": 8.0, + "indie rock": 4.0, + "seen live": 34.0, + "alternative": 79.0, + "female vocalist": 6.0, + "alternative rock": 22.0, + "screamo": 1.0, + "00s": 2.0, + "female vocalists": 72.0, + "punk": 13.0, + "Love": 2.0 + } + }, + { + "description": "Britney Spears", + "features": { + "singer-songwriter": 2.0, + "american": 9.0, + "electronica": 1.0, + "beautiful": 2.0, + "dance": 44.0, + "rnb": 3.0, + "House": 1.0, + "trance": 1.0, + "female vocalists": 44.0, + "Love": 2.0, + "black metal": 2.0, + "90s": 6.0, + "indie": 1.0, + "death metal": 1.0, + "emo": 2.0, + "pop": 100.0, + "rock": 3.0, + "electro": 2.0, + "electronic": 5.0, + "seen live": 7.0, + "Hip-Hop": 2.0, + "alternative": 2.0, + "female vocalist": 4.0, + "00s": 4.0 + } + }, + { + "description": "Travi$ Scott", + "features": { + "male vocalists": 2.0, + "american": 10.0, + "experimental": 4.0, + "electronica": 2.0, + "rap": 62.0, + "rnb": 4.0, + "soul": 4.0, + "electro": 4.0, + "hip hop": 20.0, + "seen live": 24.0, + "Hip-Hop": 100.0, + "alternative": 2.0 + } + }, + { + "description": "Oasis", + "features": { + "indie": 45.0, + "favorites": 1.0, + "male vocalists": 2.0, + "british": 61.0, + "classic rock": 3.0, + "rock": 89.0, + "pop": 7.0, + "indie rock": 12.0, + "britpop": 100.0, + "seen live": 21.0, + "alternative": 53.0, + "alternative rock": 20.0, + "00s": 2.0, + "punk": 1.0, + "hard rock": 2.0, + "90s": 10.0 + } + }, + { + "description": "A$AP Rocky", + "features": { + "male vocalists": 3.0, + "american": 9.0, + "experimental": 2.0, + "hip hop": 12.0, + "rap": 61.0, + "seen live": 30.0, + "Hip-Hop": 100.0 + } + }, + { + "description": "Nicki Minaj", + "features": { + "singer-songwriter": 2.0, + "american": 7.0, + "dance": 9.0, + "rap": 84.0, + "rnb": 51.0, + "female vocalists": 25.0, + "Love": 2.0, + "chillout": 1.0, + "pop": 44.0, + "hip hop": 39.0, + "electronic": 3.0, + "seen live": 4.0, + "Hip-Hop": 100.0, + "alternative": 2.0, + "female vocalist": 3.0 + } + }, + { + "description": "The Smiths", + "features": { + "indie": 100.0, + "favorites": 3.0, + "male vocalists": 3.0, + "singer-songwriter": 2.0, + "emo": 2.0, + "british": 32.0, + "classic rock": 4.0, + "rock": 32.0, + "pop": 10.0, + "indie rock": 22.0, + "new wave": 85.0, + "electronic": 1.0, + "britpop": 10.0, + "indie pop": 10.0, + "seen live": 2.0, + "alternative": 77.0, + "post-punk": 85.0, + "alternative rock": 21.0, + "punk": 2.0, + "Love": 2.0, + "80s": 96.0 + } + }, + { + "description": "The Strokes", + "features": { + "indie": 97.0, + "favorites": 2.0, + "male vocalists": 2.0, + "emo": 1.0, + "american": 10.0, + "british": 2.0, + "classic rock": 2.0, + "rock": 98.0, + "pop": 2.0, + "punk rock": 2.0, + "indie rock": 100.0, + "new wave": 1.0, + "britpop": 2.0, + "indie pop": 3.0, + "seen live": 42.0, + "alternative": 71.0, + "post-punk": 5.0, + "alternative rock": 59.0, + "00s": 4.0, + "punk": 4.0, + "Love": 1.0 + } + }, + { + "description": "MGMT", + "features": { + "indie": 90.0, + "male vocalists": 2.0, + "Psychedelic Rock": 4.0, + "american": 8.0, + "experimental": 5.0, + "psychedelic": 68.0, + "pop": 7.0, + "rock": 5.0, + "electronica": 5.0, + "electro": 3.0, + "synthpop": 10.0, + "indie rock": 10.0, + "electronic": 100.0, + "dance": 3.0, + "indie pop": 51.0, + "seen live": 35.0, + "alternative": 53.0, + "post-punk": 1.0, + "alternative rock": 3.0, + "00s": 3.0 + } + }, + { + "description": "Fall Out Boy", + "features": { + "indie": 14.0, + "favorites": 3.0, + "male vocalists": 3.0, + "emo": 86.0, + "american": 7.0, + "rock": 91.0, + "pop": 9.0, + "punk rock": 28.0, + "indie rock": 4.0, + "dance": 1.0, + "seen live": 39.0, + "hardcore": 3.0, + "alternative": 69.0, + "metal": 2.0, + "alternative rock": 16.0, + "screamo": 3.0, + "00s": 2.0, + "punk": 55.0, + "Love": 2.0 + } + }, + { + "description": "Dua Lipa", + "features": { + "beautiful": 8.0, + "dance": 11.0, + "pop": 100.0, + "electronic": 11.0, + "indie pop": 16.0, + "seen live": 28.0, + "british": 29.0, + "japanese": 6.0, + "rnb": 12.0, + "female vocalists": 31.0, + "indie": 9.0, + "emo": 9.0, + "synthpop": 35.0 + } + }, + { + "description": "Jonas Brothers", + "features": { + "american": 9.0, + "grindcore": 3.0, + "punk rock": 2.0, + "dance": 2.0, + "metal": 3.0, + "House": 2.0, + "Soundtrack": 2.0, + "black metal": 3.0, + "Power metal": 2.0, + "pop": 100.0, + "rock": 56.0, + "Melodic Death Metal": 3.0, + "seen live": 17.0, + "00s": 4.0, + "male vocalists": 10.0, + "female vocalists": 2.0, + "punk": 3.0, + "Love": 3.0, + "indie": 2.0, + "death metal": 4.0, + "emo": 3.0, + "metalcore": 2.0, + "hardcore": 2.0, + "alternative": 2.0, + "alternative rock": 2.0 + } + }, + { + "description": "Foster the People", + "features": { + "indie": 100.0, + "male vocalists": 4.0, + "american": 36.0, + "pop": 10.0, + "rock": 7.0, + "indie rock": 12.0, + "electronic": 8.0, + "dance": 2.0, + "indie pop": 83.0, + "seen live": 23.0, + "alternative": 65.0, + "alternative rock": 7.0, + "00s": 1.0 + } + }, + { + "description": "Bon Iver", + "features": { + "indie": 75.0, + "singer-songwriter": 70.0, + "male vocalists": 4.0, + "Mellow": 3.0, + "chillout": 2.0, + "american": 7.0, + "experimental": 2.0, + "ambient": 2.0, + "rock": 2.0, + "beautiful": 3.0, + "indie rock": 5.0, + "indie pop": 2.0, + "seen live": 36.0, + "acoustic": 59.0, + "alternative": 6.0, + "00s": 2.0, + "folk": 100.0 + } + }, + { + "description": "Major Lazer", + "features": { + "indie": 2.0, + "american": 4.0, + "experimental": 3.0, + "british": 2.0, + "electronica": 5.0, + "pop": 2.0, + "electro": 37.0, + "hip hop": 2.0, + "electronic": 85.0, + "dance": 12.0, + "seen live": 100.0, + "Hip-Hop": 4.0, + "alternative": 2.0, + "House": 3.0, + "00s": 2.0, + "funk": 4.0, + "reggae": 66.0 + } + }, + { + "description": "Two Door Cinema Club", + "features": { + "indie": 100.0, + "male vocalists": 2.0, + "british": 63.0, + "rock": 6.0, + "pop": 3.0, + "electronica": 2.0, + "synthpop": 2.0, + "indie rock": 19.0, + "electronic": 68.0, + "dance": 2.0, + "britpop": 2.0, + "indie pop": 10.0, + "seen live": 47.0, + "alternative": 53.0, + "alternative rock": 4.0, + "00s": 2.0 + } + }, + { + "description": "OneRepublic", + "features": { + "indie": 63.0, + "favorites": 2.0, + "male vocalists": 8.0, + "emo": 1.0, + "american": 12.0, + "rock": 100.0, + "pop": 21.0, + "piano": 2.0, + "indie rock": 5.0, + "indie pop": 2.0, + "seen live": 9.0, + "alternative": 94.0, + "acoustic": 1.0, + "alternative rock": 80.0, + "00s": 3.0, + "Love": 2.0 + } + }, + { + "description": "Cage the Elephant", + "features": { + "indie": 59.0, + "male vocalists": 2.0, + "american": 11.0, + "rock": 68.0, + "punk rock": 44.0, + "indie rock": 100.0, + "seen live": 41.0, + "alternative": 73.0, + "alternative rock": 26.0, + "00s": 4.0, + "punk": 7.0 + } + }, + { + "description": "Arcade Fire", + "features": { + "indie": 100.0, + "male vocalists": 2.0, + "experimental": 6.0, + "rock": 48.0, + "pop": 2.0, + "beautiful": 1.0, + "post-rock": 2.0, + "indie rock": 99.0, + "new wave": 1.0, + "indie pop": 4.0, + "seen live": 59.0, + "alternative": 76.0, + "post-punk": 3.0, + "alternative rock": 16.0, + "00s": 5.0, + "female vocalists": 2.0, + "folk": 6.0 + } + }, + { + "description": "Bruno Mars", + "features": { + "indie": 3.0, + "male vocalists": 47.0, + "singer-songwriter": 7.0, + "soul": 10.0, + "american": 8.0, + "blues": 2.0, + "pop": 100.0, + "rock": 2.0, + "hip hop": 2.0, + "dance": 2.0, + "seen live": 6.0, + "Hip-Hop": 3.0, + "rnb": 63.0, + "alternative": 2.0, + "acoustic": 1.0, + "folk": 1.0, + "funk": 4.0, + "reggae": 4.0 + } + }, + { + "description": "Carly Rae Jepsen", + "features": { + "singer-songwriter": 5.0, + "soul": 2.0, + "chillout": 2.0, + "pop": 100.0, + "synthpop": 9.0, + "electronic": 4.0, + "dance": 11.0, + "seen live": 6.0, + "acoustic": 21.0, + "female vocalist": 4.0, + "00s": 1.0, + "female vocalists": 46.0, + "folk": 26.0 + } + }, + { + "description": "Weezer", + "features": { + "indie": 50.0, + "favorites": 3.0, + "male vocalists": 2.0, + "emo": 15.0, + "american": 8.0, + "classic rock": 2.0, + "rock": 100.0, + "pop": 6.0, + "punk rock": 5.0, + "indie rock": 41.0, + "britpop": 1.0, + "indie pop": 3.0, + "seen live": 37.0, + "hardcore": 1.0, + "Grunge": 2.0, + "alternative": 86.0, + "metal": 2.0, + "alternative rock": 73.0, + "00s": 2.0, + "punk": 11.0, + "Love": 1.0, + "hard rock": 2.0, + "90s": 9.0 + } + }, + { + "description": "P!nk", + "features": { + "indie": 2.0, + "favorites": 2.0, + "singer-songwriter": 5.0, + "soul": 2.0, + "american": 11.0, + "pop": 100.0, + "rock": 59.0, + "electronic": 2.0, + "dance": 7.0, + "seen live": 14.0, + "Hip-Hop": 2.0, + "rnb": 5.0, + "alternative": 4.0, + "female vocalist": 4.0, + "alternative rock": 2.0, + "00s": 4.0, + "female vocalists": 74.0, + "punk": 3.0 + } + }, + { + "description": "System of a Down", + "features": { + "indie": 2.0, + "favorites": 1.0, + "male vocalists": 1.0, + "emo": 2.0, + "american": 5.0, + "experimental": 3.0, + "rock": 65.0, + "punk rock": 2.0, + "metalcore": 1.0, + "heavy metal": 6.0, + "Progressive metal": 3.0, + "thrash metal": 2.0, + "seen live": 18.0, + "hardcore": 3.0, + "metal": 100.0, + "alternative": 54.0, + "Progressive rock": 2.0, + "Nu Metal": 62.0, + "alternative rock": 13.0, + "industrial": 1.0, + "00s": 2.0, + "punk": 3.0, + "hard rock": 20.0, + "90s": 2.0 + } + }, + { + "description": "Adele", + "features": { + "indie": 40.0, + "singer-songwriter": 50.0, + "soul": 100.0, + "jazz": 8.0, + "chillout": 2.0, + "Mellow": 2.0, + "british": 66.0, + "blues": 3.0, + "pop": 19.0, + "rock": 2.0, + "beautiful": 1.0, + "piano": 2.0, + "britpop": 3.0, + "indie pop": 3.0, + "seen live": 2.0, + "rnb": 2.0, + "acoustic": 4.0, + "alternative": 4.0, + "female vocalist": 4.0, + "00s": 2.0, + "female vocalists": 81.0, + "Love": 2.0, + "folk": 2.0 + } + }, + { + "description": "xxxtentacion", + "features": { + "american": 11.0, + "experimental": 7.0, + "punk rock": 4.0, + "rap": 80.0, + "folk": 4.0, + "rock": 7.0, + "pop": 4.0, + "heavy metal": 4.0, + "seen live": 4.0, + "rnb": 7.0, + "emo": 49.0, + "soul": 4.0, + "hip hop": 32.0, + "new wave": 4.0, + "hardcore": 4.0, + "Hip-Hop": 100.0, + "alternative": 11.0, + "alternative rock": 7.0 + } + }, + { + "description": "The 1975", + "features": { + "male vocalists": 5.0, + "british": 69.0, + "experimental": 2.0, + "indie rock": 98.0, + "Love": 1.0, + "indie": 100.0, + "emo": 1.0, + "ambient": 1.0, + "rock": 21.0, + "pop": 9.0, + "post-rock": 1.0, + "synthpop": 5.0, + "new wave": 4.0, + "electronic": 3.0, + "britpop": 1.0, + "indie pop": 17.0, + "seen live": 73.0, + "alternative": 69.0, + "alternative rock": 20.0, + "00s": 1.0, + "funk": 1.0 + } + }, + { + "description": "Depeche Mode", + "features": { + "indie": 3.0, + "favorites": 1.0, + "male vocalists": 2.0, + "british": 13.0, + "ambient": 1.0, + "experimental": 1.0, + "classic rock": 2.0, + "rock": 16.0, + "electronica": 11.0, + "pop": 9.0, + "electro": 3.0, + "synthpop": 56.0, + "electronic": 100.0, + "new wave": 87.0, + "dance": 3.0, + "britpop": 2.0, + "seen live": 22.0, + "techno": 1.0, + "alternative": 20.0, + "Gothic": 2.0, + "post-punk": 4.0, + "alternative rock": 5.0, + "industrial": 3.0, + "00s": 2.0, + "80s": 59.0, + "90s": 4.0 + } + }, + { + "description": "The White Stripes", + "features": { + "indie": 59.0, + "favorites": 2.0, + "male vocalists": 1.0, + "singer-songwriter": 1.0, + "american": 8.0, + "experimental": 1.0, + "blues": 13.0, + "classic rock": 3.0, + "rock": 100.0, + "pop": 2.0, + "punk rock": 2.0, + "indie rock": 47.0, + "seen live": 25.0, + "Grunge": 1.0, + "alternative": 74.0, + "metal": 1.0, + "guitar": 2.0, + "Progressive rock": 1.0, + "alternative rock": 65.0, + "00s": 3.0, + "punk": 5.0, + "hard rock": 3.0, + "folk": 2.0, + "90s": 2.0 + } + }, + { + "description": "The Clash", + "features": { + "indie": 5.0, + "british": 43.0, + "classic rock": 29.0, + "rock": 43.0, + "pop": 1.0, + "punk rock": 71.0, + "indie rock": 2.0, + "new wave": 4.0, + "alternative": 9.0, + "post-punk": 3.0, + "alternative rock": 2.0, + "punk": 100.0, + "hard rock": 1.0, + "70s": 8.0, + "80s": 13.0, + "reggae": 6.0 + } + } +] \ No newline at end of file diff --git a/machine-learning/src/main/resources/kmeans/lastfm.json b/machine-learning/src/main/resources/kmeans/lastfm.json new file mode 100644 index 0000000000..4d684b2fc0 --- /dev/null +++ b/machine-learning/src/main/resources/kmeans/lastfm.json @@ -0,0 +1,490 @@ +{ + "children": [ + { + "children": [ + { + "name": "Radiohead" + }, + { + "name": "Red Hot Chili Peppers" + }, + { + "name": "Coldplay" + }, + { + "name": "Nirvana" + }, + { + "name": "Panic! at the Disco" + }, + { + "name": "The Cure" + }, + { + "name": "Linkin Park" + }, + { + "name": "Radiohead" + }, + { + "name": "Red Hot Chili Peppers" + }, + { + "name": "Coldplay" + }, + { + "name": "Nirvana" + }, + { + "name": "Panic! at the Disco" + }, + { + "name": "The Cure" + }, + { + "name": "Linkin Park" + }, + { + "name": "Muse" + }, + { + "name": "Maroon 5" + }, + { + "name": "Foo Fighters" + }, + { + "name": "Paramore" + }, + { + "name": "Oasis" + }, + { + "name": "Fall Out Boy" + }, + { + "name": "OneRepublic" + }, + { + "name": "Weezer" + }, + { + "name": "System of a Down" + }, + { + "name": "The White Stripes" + } + ], + "name": "rock, alternative" + }, + { + "children": [ + { + "name": "Lil Nas X" + }, + { + "name": "Post Malone" + }, + { + "name": "Drake" + }, + { + "name": "Kanye West" + }, + { + "name": "Kendrick Lamar" + }, + { + "name": "Tyler, the Creator" + }, + { + "name": "Eminem" + }, + { + "name": "Childish Gambino" + }, + { + "name": "Frank Ocean" + }, + { + "name": "Lil Nas X" + }, + { + "name": "Post Malone" + }, + { + "name": "Drake" + }, + { + "name": "Kanye West" + }, + { + "name": "Kendrick Lamar" + }, + { + "name": "Tyler, the Creator" + }, + { + "name": "Eminem" + }, + { + "name": "Childish Gambino" + }, + { + "name": "Frank Ocean" + }, + { + "name": "Lizzo" + }, + { + "name": "Travi$ Scott" + }, + { + "name": "A$AP Rocky" + }, + { + "name": "Nicki Minaj" + }, + { + "name": "xxxtentacion" + } + ], + "name": "Hip-Hop, rap" + }, + { + "children": [ + { + "name": "Arctic Monkeys" + }, + { + "name": "Imagine Dragons" + }, + { + "name": "The Killers" + }, + { + "name": "Gorillaz" + }, + { + "name": "The Black Keys" + }, + { + "name": "Arctic Monkeys" + }, + { + "name": "Imagine Dragons" + }, + { + "name": "The Killers" + }, + { + "name": "Gorillaz" + }, + { + "name": "The Black Keys" + }, + { + "name": "Twenty One Pilots" + }, + { + "name": "Ellie Goulding" + }, + { + "name": "Florence + the Machine" + }, + { + "name": "Vampire Weekend" + }, + { + "name": "The Smiths" + }, + { + "name": "The Strokes" + }, + { + "name": "MGMT" + }, + { + "name": "Foster the People" + }, + { + "name": "Two Door Cinema Club" + }, + { + "name": "Cage the Elephant" + }, + { + "name": "Arcade Fire" + }, + { + "name": "The 1975" + } + ], + "name": "indie, alternative" + }, + { + "children": [ + { + "name": "Ed Sheeran" + }, + { + "name": "Tame Impala" + }, + { + "name": "Ed Sheeran" + }, + { + "name": "Tame Impala" + }, + { + "name": "Green Day" + }, + { + "name": "Metallica" + }, + { + "name": "blink-182" + }, + { + "name": "Bon Iver" + }, + { + "name": "The Clash" + } + ], + "name": "rock, punk rock" + }, + { + "children": [ + { + "name": "Calvin Harris" + }, + { + "name": "The Weeknd" + }, + { + "name": "The Chainsmokers" + }, + { + "name": "Daft Punk" + }, + { + "name": "Marshmello" + }, + { + "name": "David Guetta" + }, + { + "name": "Calvin Harris" + }, + { + "name": "The Weeknd" + }, + { + "name": "The Chainsmokers" + }, + { + "name": "Daft Punk" + }, + { + "name": "Marshmello" + }, + { + "name": "David Guetta" + }, + { + "name": "Avicii" + }, + { + "name": "Kygo" + }, + { + "name": "Martin Garrix" + }, + { + "name": "Major Lazer" + }, + { + "name": "Depeche Mode" + } + ], + "name": "electronic, dance" + }, + { + "children": [ + { + "name": "Queen" + }, + { + "name": "The Beatles" + }, + { + "name": "David Bowie" + }, + { + "name": "Fleetwood Mac" + }, + { + "name": "Pink Floyd" + }, + { + "name": "The Rolling Stones" + }, + { + "name": "Led Zeppelin" + }, + { + "name": "Queen" + }, + { + "name": "The Beatles" + }, + { + "name": "David Bowie" + }, + { + "name": "Fleetwood Mac" + }, + { + "name": "Pink Floyd" + }, + { + "name": "The Rolling Stones" + }, + { + "name": "Led Zeppelin" + }, + { + "name": "Elton John" + } + ], + "name": "classic rock, rock" + }, + { + "children": [ + { + "name": "Billie Eilish" + }, + { + "name": "Ariana Grande" + }, + { + "name": "Taylor Swift" + }, + { + "name": "Beyoncé" + }, + { + "name": "Shawn Mendes" + }, + { + "name": "Rihanna" + }, + { + "name": "Lana Del Rey" + }, + { + "name": "Katy Perry" + }, + { + "name": "Lady Gaga" + }, + { + "name": "Miley Cyrus" + }, + { + "name": "Mark Ronson" + }, + { + "name": "Madonna" + }, + { + "name": "Lorde" + }, + { + "name": "Khalid" + }, + { + "name": "Billie Eilish" + }, + { + "name": "Ariana Grande" + }, + { + "name": "Taylor Swift" + }, + { + "name": "Beyoncé" + }, + { + "name": "Shawn Mendes" + }, + { + "name": "Rihanna" + }, + { + "name": "Lana Del Rey" + }, + { + "name": "Katy Perry" + }, + { + "name": "Lady Gaga" + }, + { + "name": "Miley Cyrus" + }, + { + "name": "Mark Ronson" + }, + { + "name": "Madonna" + }, + { + "name": "Lorde" + }, + { + "name": "Khalid" + }, + { + "name": "Sia" + }, + { + "name": "Sam Smith" + }, + { + "name": "Halsey" + }, + { + "name": "Michael Jackson" + }, + { + "name": "Charli XCX" + }, + { + "name": "Britney Spears" + }, + { + "name": "Dua Lipa" + }, + { + "name": "Jonas Brothers" + }, + { + "name": "Bruno Mars" + }, + { + "name": "Carly Rae Jepsen" + }, + { + "name": "P!nk" + }, + { + "name": "Adele" + } + ], + "name": "pop, female vocalists" + } + ], + "name": "Musicians" +} diff --git a/machine-learning/src/main/resources/kmeans/radial.html b/machine-learning/src/main/resources/kmeans/radial.html new file mode 100644 index 0000000000..d7f9b74cbc --- /dev/null +++ b/machine-learning/src/main/resources/kmeans/radial.html @@ -0,0 +1,54 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 352da33fee..228e0ee37c 100644 --- a/pom.xml +++ b/pom.xml @@ -339,6 +339,7 @@ algorithms-miscellaneous-1 algorithms-miscellaneous-2 algorithms-miscellaneous-3 + machine-learning algorithms-sorting animal-sniffer-mvn-plugin annotations From f8e37b316252b450a1e4801178093be02dd0da7c Mon Sep 17 00:00:00 2001 From: Ali Dehghani Date: Sun, 4 Aug 2019 20:39:13 +0430 Subject: [PATCH 2/5] Fixed the compilation failure. --- .../java/com/baeldung/ml/kmeans/Distance.java | 18 +-- .../java/com/baeldung/ml/kmeans/Errors.java | 22 +-- .../baeldung/ml/kmeans/EuclideanDistance.java | 24 ++-- .../java/com/baeldung/ml/kmeans/LastFm.java | 104 +++++++------- .../com/baeldung/ml/kmeans/LastFmService.java | 127 +++++++++--------- .../java/com/baeldung/ml/kmeans/Record.java | 78 +++++------ 6 files changed, 189 insertions(+), 184 deletions(-) diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java index 37e1c8492e..88275d67bb 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java @@ -8,13 +8,13 @@ import java.util.Map; */ public interface Distance { - /** - * Calculates the distance between two feature vectors. - * - * @param f1 The first set of features. - * @param f2 The second set of features. - * @return Calculated distance. - * @throws IllegalArgumentException If the given feature vectors are invalid. - */ - double calculate(Map f1, Map f2); + /** + * Calculates the distance between two feature vectors. + * + * @param f1 The first set of features. + * @param f2 The second set of features. + * @return Calculated distance. + * @throws IllegalArgumentException If the given feature vectors are invalid. + */ + double calculate(Map f1, Map f2); } diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java index 4b973c1146..25e70470c1 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java @@ -8,16 +8,16 @@ import java.util.Map; */ public class Errors { - public static double sse(Map> clustered, Distance distance) { - double sum = 0; - for (Map.Entry> entry : clustered.entrySet()) { - Centroid centroid = entry.getKey(); - for (Record record : entry.getValue()) { - double d = distance.calculate(centroid.getCoordinates(), record.getFeatures()); - sum += Math.pow(d, 2); - } - } + public static double sse(Map> clustered, Distance distance) { + double sum = 0; + for (Map.Entry> entry : clustered.entrySet()) { + Centroid centroid = entry.getKey(); + for (Record record : entry.getValue()) { + double d = distance.calculate(centroid.getCoordinates(), record.getFeatures()); + sum += Math.pow(d, 2); + } + } - return sum; - } + return sum; + } } diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java index 1946c508b4..7efc6e617b 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java @@ -7,19 +7,19 @@ import java.util.Map; */ public class EuclideanDistance implements Distance { - @Override - public double calculate(Map f1, Map f2) { - if (f1 == null || f2 == null) - throw new IllegalArgumentException("Feature vectors can't be null"); + @Override + public double calculate(Map f1, Map f2) { + if (f1 == null || f2 == null) + throw new IllegalArgumentException("Feature vectors can't be null"); - double sum = 0; - for (String key : f1.keySet()) { - Double v1 = f1.get(key); - Double v2 = f2.get(key); + double sum = 0; + for (String key : f1.keySet()) { + Double v1 = f1.get(key); + Double v2 = f2.get(key); - if (v1 != null && v2 != null) sum += Math.pow(v1 - v2, 2); - } + if (v1 != null && v2 != null) sum += Math.pow(v1 - v2, 2); + } - return Math.sqrt(sum); - } + return Math.sqrt(sum); + } } diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java index cdaf2170cd..0ff9d3cff4 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java @@ -13,40 +13,40 @@ import static java.util.stream.Collectors.toSet; public class LastFm { - private static OkHttpClient okHttp = new OkHttpClient.Builder() - .addInterceptor(new LastFmService.Authenticator("put your API key here")) - .build(); + private static OkHttpClient okHttp = new OkHttpClient.Builder() + .addInterceptor(new LastFmService.Authenticator("put your API key here")) + .build(); - private static Retrofit retrofit = new Retrofit.Builder().client(okHttp) - .addConverterFactory(JacksonConverterFactory.create()) - .baseUrl("http://ws.audioscrobbler.com/") - .build(); + private static Retrofit retrofit = new Retrofit.Builder().client(okHttp) + .addConverterFactory(JacksonConverterFactory.create()) + .baseUrl("http://ws.audioscrobbler.com/") + .build(); - private static LastFmService lastFm = retrofit.create(LastFmService.class); + private static LastFmService lastFm = retrofit.create(LastFmService.class); - private static ObjectMapper mapper = new ObjectMapper(); + private static ObjectMapper mapper = new ObjectMapper(); - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws IOException { List artists = getTop100Artists(); Set tags = getTop100Tags(); List records = datasetWithTaggedArtists(artists, tags); Map> clusters = KMeans.fit(records, 7, new EuclideanDistance(), 1000); - // Print the cluster configuration - clusters.forEach((key, value) -> { - System.out.println("------------------------------ CLUSTER -----------------------------------"); + // Print the cluster configuration + clusters.forEach((key, value) -> { + System.out.println("------------------------------ CLUSTER -----------------------------------"); - System.out.println(sortedCentroid(key)); - String members = String.join(", ", value.stream().map(Record::getDescription).collect(toSet())); - System.out.print(members); + System.out.println(sortedCentroid(key)); + String members = String.join(", ", value.stream().map(Record::getDescription).collect(toSet())); + System.out.print(members); - System.out.println(); - System.out.println(); - }); + System.out.println(); + System.out.println(); + }); Map json = convertToD3CompatibleMap(clusters); System.out.println(mapper.writeValueAsString(json)); - } + } private static Map convertToD3CompatibleMap(Map> clusters) { Map json = new HashMap<>(); @@ -69,45 +69,45 @@ public class LastFm { } private static String dominantGenre(Centroid centroid) { - return centroid.getCoordinates().keySet().stream().limit(2).collect(Collectors.joining(", ")); - } + return centroid.getCoordinates().keySet().stream().limit(2).collect(Collectors.joining(", ")); + } - private static Centroid sortedCentroid(Centroid key) { - List> entries = new ArrayList<>(key.getCoordinates().entrySet()); - entries.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue())); + private static Centroid sortedCentroid(Centroid key) { + List> entries = new ArrayList<>(key.getCoordinates().entrySet()); + entries.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue())); - Map sorted = new LinkedHashMap<>(); - for (Map.Entry entry : entries) { - sorted.put(entry.getKey(), entry.getValue()); - } + Map sorted = new LinkedHashMap<>(); + for (Map.Entry entry : entries) { + sorted.put(entry.getKey(), entry.getValue()); + } - return new Centroid(sorted); - } + return new Centroid(sorted); + } - private static List datasetWithTaggedArtists(List artists, - Set topTags) throws IOException { - List records = new ArrayList<>(); - for (String artist : artists) { - Map tags = lastFm.topTagsFor(artist).execute().body().all(); + private static List datasetWithTaggedArtists(List artists, + Set topTags) throws IOException { + List records = new ArrayList<>(); + for (String artist : artists) { + Map tags = lastFm.topTagsFor(artist).execute().body().all(); - // Only keep popular tags. - tags.entrySet().removeIf(e -> !topTags.contains(e.getKey())); + // Only keep popular tags. + tags.entrySet().removeIf(e -> !topTags.contains(e.getKey())); - records.add(new Record(artist, tags)); - } - return records; - } + records.add(new Record(artist, tags)); + } + return records; + } - private static Set getTop100Tags() throws IOException { - return lastFm.topTags().execute().body().all(); - } + private static Set getTop100Tags() throws IOException { + return lastFm.topTags().execute().body().all(); + } - private static List getTop100Artists() throws IOException { - List artists = new ArrayList<>(); - for (int i = 1; i <= 2; i++) { - artists.addAll(lastFm.topArtists(i).execute().body().all()); - } + private static List getTop100Artists() throws IOException { + List artists = new ArrayList<>(); + for (int i = 1; i <= 2; i++) { + artists.addAll(lastFm.topArtists(i).execute().body().all()); + } - return artists; - } + return artists; + } } diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java index 0cc8e9e285..4e2bf6bd92 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java @@ -8,6 +8,8 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; import okhttp3.HttpUrl; import okhttp3.Interceptor; import okhttp3.Request; @@ -16,86 +18,89 @@ import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; import static java.util.stream.Collectors.toList; public interface LastFmService { - @GET("/2.0/?method=chart.gettopartists&format=json&limit=50") - Call topArtists(@Query("page") int page); + @GET("/2.0/?method=chart.gettopartists&format=json&limit=50") + Call topArtists(@Query("page") int page); - @GET("/2.0/?method=artist.gettoptags&format=json&limit=20&autocorrect=1") - Call topTagsFor(@Query("artist") String artist); + @GET("/2.0/?method=artist.gettoptags&format=json&limit=20&autocorrect=1") + Call topTagsFor(@Query("artist") String artist); - @GET("/2.0/?method=chart.gettoptags&format=json&limit=100") - Call topTags(); + @GET("/2.0/?method=chart.gettoptags&format=json&limit=100") + Call topTags(); - /** - * HTTP interceptor to intercept all HTTP requests and add the API key to them. - */ - class Authenticator implements Interceptor { + /** + * HTTP interceptor to intercept all HTTP requests and add the API key to them. + */ + class Authenticator implements Interceptor { - private final String apiKey; + private final String apiKey; - Authenticator(String apiKey) { - this.apiKey = apiKey; - } + Authenticator(String apiKey) { + this.apiKey = apiKey; + } - @Override - public Response intercept(Chain chain) throws IOException { - HttpUrl url = chain.request().url().newBuilder().addQueryParameter("api_key", apiKey).build(); - Request request = chain.request().newBuilder().url(url).build(); + @Override + public Response intercept(Chain chain) throws IOException { + HttpUrl url = chain.request().url().newBuilder().addQueryParameter("api_key", apiKey).build(); + Request request = chain.request().newBuilder().url(url).build(); - return chain.proceed(request); - } - } + return chain.proceed(request); + } + } - @JsonAutoDetect(fieldVisibility = ANY) - class TopTags { + @JsonAutoDetect(fieldVisibility = ANY) + class TopTags { - private Map tags; + private Map tags; - @SuppressWarnings("unchecked") - public Set all() { - List> topTags = (List>) tags.get("tag"); - return topTags.stream().map(e -> ((String) e.get("name"))).collect(Collectors.toSet()); - } - } + @SuppressWarnings("unchecked") + public Set all() { + List> topTags = (List>) tags.get("tag"); + return topTags.stream().map(e -> ((String) e.get("name"))).collect(Collectors.toSet()); + } + } - @JsonAutoDetect(fieldVisibility = ANY) - class Tags { + @JsonAutoDetect(fieldVisibility = ANY) + class Tags { - @JsonProperty("toptags") - private Map topTags; + @JsonProperty("toptags") + private Map topTags; - @SuppressWarnings("unchecked") - public Map all() { - try { - Map all = new HashMap<>(); - List> tags = (List>) topTags.get("tag"); - for (Map tag : tags) { - all.put(((String) tag.get("name")), ((Integer) tag.get("count")).doubleValue()); - } + @SuppressWarnings("unchecked") + public Map all() { + try { + Map all = new HashMap<>(); + List> tags = (List>) topTags.get("tag"); + for (Map tag : tags) { + all.put(((String) tag.get("name")), ((Integer) tag.get("count")).doubleValue()); + } - return all; - } catch (Exception e) { - return Collections.emptyMap(); - } - } - } + return all; + } + catch (Exception e) { + return Collections.emptyMap(); + } + } + } - @JsonAutoDetect(fieldVisibility = ANY) - class Artists { + @JsonAutoDetect(fieldVisibility = ANY) + class Artists { - private Map artists; + private Map artists; - @SuppressWarnings("unchecked") - public List all() { - try { - List> artists = (List>) this.artists.get("artist"); - return artists.stream().map(e -> ((String) e.get("name"))).collect(toList()); - } catch (Exception e) { - return Collections.emptyList(); - } - } - } + @SuppressWarnings("unchecked") + public List all() { + try { + List> artists = (List>) this.artists.get("artist"); + return artists.stream().map(e -> ((String) e.get("name"))).collect(toList()); + } + catch (Exception e) { + return Collections.emptyList(); + } + } + } } diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java index 7208526136..0e936d49e6 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java +++ b/machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java @@ -9,52 +9,52 @@ import java.util.Objects; */ public class Record { - /** - * The record description. For example, this can be the artist name for the famous musician - * example. - */ - private final String description; + /** + * The record description. For example, this can be the artist name for the famous musician + * example. + */ + private final String description; - /** - * Encapsulates all attributes and their corresponding values, i.e. features. - */ - private final Map features; + /** + * Encapsulates all attributes and their corresponding values, i.e. features. + */ + private final Map features; - public Record(String description, Map features) { - this.description = description; - this.features = features; - } + public Record(String description, Map features) { + this.description = description; + this.features = features; + } - public Record(Map features) { - this("", features); - } + public Record(Map features) { + this("", features); + } - public String getDescription() { - return description; - } + public String getDescription() { + return description; + } - public Map getFeatures() { - return features; - } + public Map getFeatures() { + return features; + } - @Override - public String toString() { - String prefix = description == null || description.trim().isEmpty() ? "Record" : description; + @Override + public String toString() { + String prefix = description == null || description.trim().isEmpty() ? "Record" : description; - return prefix + ": " + features; - } + return prefix + ": " + features; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Record record = (Record) o; - return Objects.equals(getDescription(), record.getDescription()) && - Objects.equals(getFeatures(), record.getFeatures()); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Record record = (Record) o; + return Objects.equals(getDescription(), record.getDescription()) && + Objects.equals(getFeatures(), record.getFeatures()); + } - @Override - public int hashCode() { - return Objects.hash(getDescription(), getFeatures()); - } + @Override + public int hashCode() { + return Objects.hash(getDescription(), getFeatures()); + } } From f615f9aeae01ac91dc08a6f7712e49dcdf93fef2 Mon Sep 17 00:00:00 2001 From: Ali Dehghani Date: Wed, 7 Aug 2019 20:36:36 +0430 Subject: [PATCH 3/5] Removed the ML module! --- algorithms-miscellaneous-3/pom.xml | 12 +++++++ .../baeldung/algorithms}/kmeans/Centroid.java | 2 +- .../baeldung/algorithms}/kmeans/Distance.java | 2 +- .../baeldung/algorithms}/kmeans/Errors.java | 2 +- .../algorithms}/kmeans/EuclideanDistance.java | 2 +- .../baeldung/algorithms}/kmeans/KMeans.java | 9 +++-- .../baeldung/algorithms}/kmeans/LastFm.java | 16 ++++++--- .../algorithms}/kmeans/LastFmService.java | 2 +- .../baeldung/algorithms}/kmeans/Record.java | 2 +- .../src/main/resources/kmeans/artists.json | 0 .../src/main/resources/kmeans/lastfm.json | 0 .../src/main/resources/kmeans/radial.html | 0 machine-learning/pom.xml | 34 ------------------- pom.xml | 1 - 14 files changed, 36 insertions(+), 48 deletions(-) rename {machine-learning/src/main/java/com/baeldung/ml => algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms}/kmeans/Centroid.java (95%) rename {machine-learning/src/main/java/com/baeldung/ml => algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms}/kmeans/Distance.java (93%) rename {machine-learning/src/main/java/com/baeldung/ml => algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms}/kmeans/Errors.java (93%) rename {machine-learning/src/main/java/com/baeldung/ml => algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms}/kmeans/EuclideanDistance.java (93%) rename {machine-learning/src/main/java/com/baeldung/ml => algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms}/kmeans/KMeans.java (97%) rename {machine-learning/src/main/java/com/baeldung/ml => algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms}/kmeans/LastFm.java (94%) rename {machine-learning/src/main/java/com/baeldung/ml => algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms}/kmeans/LastFmService.java (98%) rename {machine-learning/src/main/java/com/baeldung/ml => algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms}/kmeans/Record.java (97%) rename {machine-learning => algorithms-miscellaneous-3}/src/main/resources/kmeans/artists.json (100%) rename {machine-learning => algorithms-miscellaneous-3}/src/main/resources/kmeans/lastfm.json (100%) rename {machine-learning => algorithms-miscellaneous-3}/src/main/resources/kmeans/radial.html (100%) delete mode 100644 machine-learning/pom.xml diff --git a/algorithms-miscellaneous-3/pom.xml b/algorithms-miscellaneous-3/pom.xml index 3cebdd09ac..1e5ba6650a 100644 --- a/algorithms-miscellaneous-3/pom.xml +++ b/algorithms-miscellaneous-3/pom.xml @@ -30,6 +30,17 @@ guava ${guava.version} + + + com.squareup.retrofit2 + retrofit + ${retrofit.version} + + + com.squareup.retrofit2 + converter-jackson + ${retrofit.version} + @@ -48,5 +59,6 @@ 3.9.0 4.3 28.0-jre + 2.6.0 \ No newline at end of file diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Centroid.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java similarity index 95% rename from machine-learning/src/main/java/com/baeldung/ml/kmeans/Centroid.java rename to algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java index 922a19d861..9f3aca7916 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Centroid.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java @@ -1,4 +1,4 @@ -package com.baeldung.ml.kmeans; +package com.baeldung.algorithms.kmeans; import java.util.Map; import java.util.Objects; diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Distance.java similarity index 93% rename from machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java rename to algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Distance.java index 88275d67bb..103eedb732 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Distance.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Distance.java @@ -1,4 +1,4 @@ -package com.baeldung.ml.kmeans; +package com.baeldung.algorithms.kmeans; import java.util.Map; diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Errors.java similarity index 93% rename from machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java rename to algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Errors.java index 25e70470c1..0fbe24c5ad 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Errors.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Errors.java @@ -1,4 +1,4 @@ -package com.baeldung.ml.kmeans; +package com.baeldung.algorithms.kmeans; import java.util.List; import java.util.Map; diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java similarity index 93% rename from machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java rename to algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java index 7efc6e617b..62d24feedf 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/EuclideanDistance.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java @@ -1,4 +1,4 @@ -package com.baeldung.ml.kmeans; +package com.baeldung.algorithms.kmeans; import java.util.Map; diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/KMeans.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java similarity index 97% rename from machine-learning/src/main/java/com/baeldung/ml/kmeans/KMeans.java rename to algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java index 6b9e513a95..d8ab70b0fd 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/KMeans.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java @@ -1,6 +1,11 @@ -package com.baeldung.ml.kmeans; +package com.baeldung.algorithms.kmeans; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFm.java similarity index 94% rename from machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java rename to algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFm.java index 0ff9d3cff4..7d241d3a79 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFm.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFm.java @@ -1,14 +1,20 @@ -package com.baeldung.ml.kmeans; +package com.baeldung.algorithms.kmeans; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.converter.jackson.JacksonConverterFactory; -import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; - import static java.util.stream.Collectors.toSet; public class LastFm { diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFmService.java similarity index 98% rename from machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java rename to algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFmService.java index 4e2bf6bd92..cfc8e8d478 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/LastFmService.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFmService.java @@ -1,4 +1,4 @@ -package com.baeldung.ml.kmeans; +package com.baeldung.algorithms.kmeans; import java.io.IOException; import java.util.Collections; diff --git a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java similarity index 97% rename from machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java rename to algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java index 0e936d49e6..6aa2c3ba90 100644 --- a/machine-learning/src/main/java/com/baeldung/ml/kmeans/Record.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java @@ -1,4 +1,4 @@ -package com.baeldung.ml.kmeans; +package com.baeldung.algorithms.kmeans; import java.util.Map; import java.util.Objects; diff --git a/machine-learning/src/main/resources/kmeans/artists.json b/algorithms-miscellaneous-3/src/main/resources/kmeans/artists.json similarity index 100% rename from machine-learning/src/main/resources/kmeans/artists.json rename to algorithms-miscellaneous-3/src/main/resources/kmeans/artists.json diff --git a/machine-learning/src/main/resources/kmeans/lastfm.json b/algorithms-miscellaneous-3/src/main/resources/kmeans/lastfm.json similarity index 100% rename from machine-learning/src/main/resources/kmeans/lastfm.json rename to algorithms-miscellaneous-3/src/main/resources/kmeans/lastfm.json diff --git a/machine-learning/src/main/resources/kmeans/radial.html b/algorithms-miscellaneous-3/src/main/resources/kmeans/radial.html similarity index 100% rename from machine-learning/src/main/resources/kmeans/radial.html rename to algorithms-miscellaneous-3/src/main/resources/kmeans/radial.html diff --git a/machine-learning/pom.xml b/machine-learning/pom.xml deleted file mode 100644 index 2753de2ff6..0000000000 --- a/machine-learning/pom.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - com.baeldung - parent-modules - 1.0.0-SNAPSHOT - - - 4.0.0 - machine-learning - 0.0.1-SNAPSHOT - Machine Learning - Host for all Machine Learning Algorithms - - - - com.squareup.retrofit2 - retrofit - ${retrofit.version} - - - com.squareup.retrofit2 - converter-jackson - ${retrofit.version} - - - - - 2.6.0 - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 228e0ee37c..352da33fee 100644 --- a/pom.xml +++ b/pom.xml @@ -339,7 +339,6 @@ algorithms-miscellaneous-1 algorithms-miscellaneous-2 algorithms-miscellaneous-3 - machine-learning algorithms-sorting animal-sniffer-mvn-plugin annotations From 5a10f94946ae4bcccfeb1b1fcbdeeb2c7433ba37 Mon Sep 17 00:00:00 2001 From: Ali Dehghani Date: Wed, 7 Aug 2019 20:45:52 +0430 Subject: [PATCH 4/5] Fixed formatting issues. --- algorithms-miscellaneous-3/pom.xml | 44 +-- .../baeldung/algorithms/kmeans/Centroid.java | 50 +-- .../baeldung/algorithms/kmeans/Distance.java | 18 +- .../baeldung/algorithms/kmeans/Errors.java | 22 +- .../algorithms/kmeans/EuclideanDistance.java | 23 +- .../baeldung/algorithms/kmeans/KMeans.java | 338 +++++++++--------- .../baeldung/algorithms/kmeans/LastFm.java | 179 ++++++---- .../algorithms/kmeans/LastFmService.java | 138 +++---- .../baeldung/algorithms/kmeans/Record.java | 79 ++-- .../src/main/resources/kmeans/radial.html | 28 +- 10 files changed, 489 insertions(+), 430 deletions(-) diff --git a/algorithms-miscellaneous-3/pom.xml b/algorithms-miscellaneous-3/pom.xml index 1e5ba6650a..888f8e2e2c 100644 --- a/algorithms-miscellaneous-3/pom.xml +++ b/algorithms-miscellaneous-3/pom.xml @@ -1,5 +1,5 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 algorithms-miscellaneous-3 0.0.1-SNAPSHOT @@ -18,29 +18,29 @@ ${org.assertj.core.version} test - + - org.apache.commons - commons-collections4 - ${commons-collections4.version} - - - - com.google.guava - guava - ${guava.version} + org.apache.commons + commons-collections4 + ${commons-collections4.version} - - com.squareup.retrofit2 - retrofit - ${retrofit.version} - - - com.squareup.retrofit2 - converter-jackson - ${retrofit.version} - + + com.google.guava + guava + ${guava.version} + + + + com.squareup.retrofit2 + retrofit + ${retrofit.version} + + + com.squareup.retrofit2 + converter-jackson + ${retrofit.version} + @@ -59,6 +59,6 @@ 3.9.0 4.3 28.0-jre - 2.6.0 + 2.6.0 \ No newline at end of file diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java index 9f3aca7916..462936541b 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java @@ -8,34 +8,34 @@ import java.util.Objects; */ public class Centroid { - /** - * The centroid coordinates. - */ - private final Map coordinates; + /** + * The centroid coordinates. + */ + private final Map coordinates; - public Centroid(Map coordinates) { - this.coordinates = coordinates; - } + public Centroid(Map coordinates) { + this.coordinates = coordinates; + } - public Map getCoordinates() { - return coordinates; - } + public Map getCoordinates() { + return coordinates; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Centroid centroid = (Centroid) o; - return Objects.equals(getCoordinates(), centroid.getCoordinates()); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Centroid centroid = (Centroid) o; + return Objects.equals(getCoordinates(), centroid.getCoordinates()); + } - @Override - public int hashCode() { - return Objects.hash(getCoordinates()); - } + @Override + public int hashCode() { + return Objects.hash(getCoordinates()); + } - @Override - public String toString() { - return "Centroid " + coordinates; - } + @Override + public String toString() { + return "Centroid " + coordinates; + } } diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Distance.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Distance.java index 103eedb732..30723cb6b3 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Distance.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Distance.java @@ -8,13 +8,13 @@ import java.util.Map; */ public interface Distance { - /** - * Calculates the distance between two feature vectors. - * - * @param f1 The first set of features. - * @param f2 The second set of features. - * @return Calculated distance. - * @throws IllegalArgumentException If the given feature vectors are invalid. - */ - double calculate(Map f1, Map f2); + /** + * Calculates the distance between two feature vectors. + * + * @param f1 The first set of features. + * @param f2 The second set of features. + * @return Calculated distance. + * @throws IllegalArgumentException If the given feature vectors are invalid. + */ + double calculate(Map f1, Map f2); } diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Errors.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Errors.java index 0fbe24c5ad..3228876051 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Errors.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Errors.java @@ -8,16 +8,16 @@ import java.util.Map; */ public class Errors { - public static double sse(Map> clustered, Distance distance) { - double sum = 0; - for (Map.Entry> entry : clustered.entrySet()) { - Centroid centroid = entry.getKey(); - for (Record record : entry.getValue()) { - double d = distance.calculate(centroid.getCoordinates(), record.getFeatures()); - sum += Math.pow(d, 2); - } - } + public static double sse(Map> clustered, Distance distance) { + double sum = 0; + for (Map.Entry> entry : clustered.entrySet()) { + Centroid centroid = entry.getKey(); + for (Record record : entry.getValue()) { + double d = distance.calculate(centroid.getCoordinates(), record.getFeatures()); + sum += Math.pow(d, 2); + } + } - return sum; - } + return sum; + } } diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java index 62d24feedf..faccd97599 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java @@ -7,19 +7,18 @@ import java.util.Map; */ public class EuclideanDistance implements Distance { - @Override - public double calculate(Map f1, Map f2) { - if (f1 == null || f2 == null) - throw new IllegalArgumentException("Feature vectors can't be null"); + @Override + public double calculate(Map f1, Map f2) { + if (f1 == null || f2 == null) throw new IllegalArgumentException("Feature vectors can't be null"); - double sum = 0; - for (String key : f1.keySet()) { - Double v1 = f1.get(key); - Double v2 = f2.get(key); + double sum = 0; + for (String key : f1.keySet()) { + Double v1 = f1.get(key); + Double v2 = f2.get(key); - if (v1 != null && v2 != null) sum += Math.pow(v1 - v2, 2); - } + if (v1 != null && v2 != null) sum += Math.pow(v1 - v2, 2); + } - return Math.sqrt(sum); - } + return Math.sqrt(sum); + } } diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java index d8ab70b0fd..e1152a67d6 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java @@ -17,200 +17,208 @@ import static java.util.stream.Collectors.toSet; */ public class KMeans { - private KMeans() { - throw new IllegalAccessError("You shouldn't call this constructor"); - } + private KMeans() { + throw new IllegalAccessError("You shouldn't call this constructor"); + } - /** - * Will be used to generate random numbers. - */ - private static final Random random = new Random(); + /** + * Will be used to generate random numbers. + */ + private static final Random random = new Random(); - /** - * Performs the K-Means clustering algorithm on the given dataset. - * - * @param records The dataset. - * @param k Number of Clusters. - * @param distance To calculate the distance between two items. - * @param maxIterations Upper bound for the number of iterations. - * @return K clusters along with their features. - */ - public static Map> fit(List records, int k, Distance distance, int maxIterations) { - applyPreconditions(records, k, distance, maxIterations); + /** + * Performs the K-Means clustering algorithm on the given dataset. + * + * @param records The dataset. + * @param k Number of Clusters. + * @param distance To calculate the distance between two items. + * @param maxIterations Upper bound for the number of iterations. + * @return K clusters along with their features. + */ + public static Map> fit(List records, int k, Distance distance, int maxIterations) { + applyPreconditions(records, k, distance, maxIterations); - List centroids = randomCentroids(records, k); - Map> clusters = new HashMap<>(); - Map> lastState = new HashMap<>(); + List centroids = randomCentroids(records, k); + Map> clusters = new HashMap<>(); + Map> lastState = new HashMap<>(); - // iterate for a pre-defined number of times - for (int i = 0; i < maxIterations; i++) { - boolean isLastIteration = i == maxIterations - 1; + // iterate for a pre-defined number of times + for (int i = 0; i < maxIterations; i++) { + boolean isLastIteration = i == maxIterations - 1; - // in each iteration we should find the nearest centroid for each record - for (Record record : records) { - Centroid centroid = nearestCentroid(record, centroids, distance); - assignToCluster(clusters, record, centroid); - } + // in each iteration we should find the nearest centroid for each record + for (Record record : records) { + Centroid centroid = nearestCentroid(record, centroids, distance); + assignToCluster(clusters, record, centroid); + } - // if the assignment does not change, then the algorithm terminates - boolean shouldTerminate = isLastIteration || clusters.equals(lastState); - lastState = clusters; - if (shouldTerminate) break; + // if the assignment does not change, then the algorithm terminates + boolean shouldTerminate = isLastIteration || clusters.equals(lastState); + lastState = clusters; + if (shouldTerminate) break; - // at the end of each iteration we should relocate the centroids - centroids = relocateCentroids(clusters); - clusters = new HashMap<>(); - } + // at the end of each iteration we should relocate the centroids + centroids = relocateCentroids(clusters); + clusters = new HashMap<>(); + } - return lastState; - } + return lastState; + } - /** - * Move all cluster centroids to the average of all assigned features. - * - * @param clusters The current cluster configuration. - * @return Collection of new and relocated centroids. - */ - private static List relocateCentroids(Map> clusters) { - return clusters.entrySet().stream() - .map(e -> average(e.getKey(), e.getValue())).collect(toList()); - } + /** + * Move all cluster centroids to the average of all assigned features. + * + * @param clusters The current cluster configuration. + * @return Collection of new and relocated centroids. + */ + private static List relocateCentroids(Map> clusters) { + return clusters + .entrySet() + .stream() + .map(e -> average(e.getKey(), e.getValue())) + .collect(toList()); + } - /** - * Moves the given centroid to the average position of all assigned features. If - * the centroid has no feature in its cluster, then there would be no need for a - * relocation. Otherwise, for each entry we calculate the average of all records - * first by summing all the entries and then dividing the final summation value by - * the number of records. - * - * @param centroid The centroid to move. - * @param records The assigned features. - * @return The moved centroid. - */ - private static Centroid average(Centroid centroid, List records) { - // if this cluster is empty, then we shouldn't move the centroid - if (records == null || records.isEmpty()) return centroid; + /** + * Moves the given centroid to the average position of all assigned features. If + * the centroid has no feature in its cluster, then there would be no need for a + * relocation. Otherwise, for each entry we calculate the average of all records + * first by summing all the entries and then dividing the final summation value by + * the number of records. + * + * @param centroid The centroid to move. + * @param records The assigned features. + * @return The moved centroid. + */ + private static Centroid average(Centroid centroid, List records) { + // if this cluster is empty, then we shouldn't move the centroid + if (records == null || records.isEmpty()) return centroid; - // Since some records don't have all possible attributes, we initialize - // average coordinates equal to current centroid coordinates - Map average = centroid.getCoordinates(); + // Since some records don't have all possible attributes, we initialize + // average coordinates equal to current centroid coordinates + Map average = centroid.getCoordinates(); - // The average function works correctly if we clear all coordinates corresponding - // to present record attributes - records.stream().flatMap(e -> e.getFeatures().keySet().stream()) - .forEach(k -> average.put(k, 0.0)); + // The average function works correctly if we clear all coordinates corresponding + // to present record attributes + records + .stream() + .flatMap(e -> e + .getFeatures() + .keySet() + .stream()) + .forEach(k -> average.put(k, 0.0)); - for (Record record : records) { - record.getFeatures().forEach( - (k, v) -> average.compute(k, (k1, currentValue) -> v + currentValue) - ); - } + for (Record record : records) { + record + .getFeatures() + .forEach((k, v) -> average.compute(k, (k1, currentValue) -> v + currentValue)); + } - average.forEach((k, v) -> average.put(k, v / records.size())); + average.forEach((k, v) -> average.put(k, v / records.size())); - return new Centroid(average); - } + return new Centroid(average); + } - /** - * Assigns a feature vector to the given centroid. If this is the first assignment for this centroid, - * first we should create the list. - * - * @param clusters The current cluster configuration. - * @param record The feature vector. - * @param centroid The centroid. - */ - private static void assignToCluster(Map> clusters, - Record record, Centroid centroid) { - clusters.compute(centroid, (key, list) -> { - if (list == null) { - list = new ArrayList<>(); - } + /** + * Assigns a feature vector to the given centroid. If this is the first assignment for this centroid, + * first we should create the list. + * + * @param clusters The current cluster configuration. + * @param record The feature vector. + * @param centroid The centroid. + */ + private static void assignToCluster(Map> clusters, Record record, Centroid centroid) { + clusters.compute(centroid, (key, list) -> { + if (list == null) { + list = new ArrayList<>(); + } - list.add(record); - return list; - }); - } + list.add(record); + return list; + }); + } - /** - * With the help of the given distance calculator, iterates through centroids and finds the - * nearest one to the given record. - * - * @param record The feature vector to find a centroid for. - * @param centroids Collection of all centroids. - * @param distance To calculate the distance between two items. - * @return The nearest centroid to the given feature vector. - */ - private static Centroid nearestCentroid(Record record, List centroids, - Distance distance) { - double minimumDistance = Double.MAX_VALUE; - Centroid nearest = null; + /** + * With the help of the given distance calculator, iterates through centroids and finds the + * nearest one to the given record. + * + * @param record The feature vector to find a centroid for. + * @param centroids Collection of all centroids. + * @param distance To calculate the distance between two items. + * @return The nearest centroid to the given feature vector. + */ + private static Centroid nearestCentroid(Record record, List centroids, Distance distance) { + double minimumDistance = Double.MAX_VALUE; + Centroid nearest = null; - for (Centroid centroid : centroids) { - double currentDistance = distance.calculate(record.getFeatures(), centroid.getCoordinates()); + for (Centroid centroid : centroids) { + double currentDistance = distance.calculate(record.getFeatures(), centroid.getCoordinates()); - if (currentDistance < minimumDistance) { - minimumDistance = currentDistance; - nearest = centroid; - } - } + if (currentDistance < minimumDistance) { + minimumDistance = currentDistance; + nearest = centroid; + } + } - return nearest; - } + return nearest; + } - /** - * Generates k random centroids. Before kicking-off the centroid generation process, - * first we calculate the possible value range for each attribute. Then when - * we're going to generate the centroids, we generate random coordinates in - * the [min, max] range for each attribute. - * - * @param records The dataset which helps to calculate the [min, max] range for - * each attribute. - * @param k Number of clusters. - * @return Collections of randomly generated centroids. - */ - private static List randomCentroids(List records, int k) { - List centroids = new ArrayList<>(); - Map maxs = new HashMap<>(); - Map mins = new HashMap<>(); + /** + * Generates k random centroids. Before kicking-off the centroid generation process, + * first we calculate the possible value range for each attribute. Then when + * we're going to generate the centroids, we generate random coordinates in + * the [min, max] range for each attribute. + * + * @param records The dataset which helps to calculate the [min, max] range for + * each attribute. + * @param k Number of clusters. + * @return Collections of randomly generated centroids. + */ + private static List randomCentroids(List records, int k) { + List centroids = new ArrayList<>(); + Map maxs = new HashMap<>(); + Map mins = new HashMap<>(); - for (Record record : records) { - record.getFeatures().forEach((key, value) -> { - // compares the value with the current max and choose the bigger value between them - maxs.compute(key, (k1, max) -> max == null || value > max ? value : max); + for (Record record : records) { + record + .getFeatures() + .forEach((key, value) -> { + // compares the value with the current max and choose the bigger value between them + maxs.compute(key, (k1, max) -> max == null || value > max ? value : max); - // compare the value with the current min and choose the smaller value between them - mins.compute(key, (k1, min) -> min == null || value < min ? value : min); - }); - } + // compare the value with the current min and choose the smaller value between them + mins.compute(key, (k1, min) -> min == null || value < min ? value : min); + }); + } - Set attributes = records.stream() - .flatMap(e -> e.getFeatures().keySet().stream()).collect(toSet()); - for (int i = 0; i < k; i++) { - Map coordinates = new HashMap<>(); - for (String attribute : attributes) { - double max = maxs.get(attribute); - double min = mins.get(attribute); - coordinates.put(attribute, random.nextDouble() * (max - min) + min); - } + Set attributes = records + .stream() + .flatMap(e -> e + .getFeatures() + .keySet() + .stream()) + .collect(toSet()); + for (int i = 0; i < k; i++) { + Map coordinates = new HashMap<>(); + for (String attribute : attributes) { + double max = maxs.get(attribute); + double min = mins.get(attribute); + coordinates.put(attribute, random.nextDouble() * (max - min) + min); + } - centroids.add(new Centroid(coordinates)); - } + centroids.add(new Centroid(coordinates)); + } - return centroids; - } + return centroids; + } - private static void applyPreconditions(List records, int k, - Distance distance, int maxIterations) { - if (records == null || records.isEmpty()) - throw new IllegalArgumentException("The dataset can't be empty"); + private static void applyPreconditions(List records, int k, Distance distance, int maxIterations) { + if (records == null || records.isEmpty()) throw new IllegalArgumentException("The dataset can't be empty"); - if (k <= 1) - throw new IllegalArgumentException("It doesn't make sense to have less than or equal to 1 cluster"); + if (k <= 1) throw new IllegalArgumentException("It doesn't make sense to have less than or equal to 1 cluster"); - if (distance == null) - throw new IllegalArgumentException("The distance calculator is required"); + if (distance == null) throw new IllegalArgumentException("The distance calculator is required"); - if (maxIterations <= 0) - throw new IllegalArgumentException("Max iterations should be a positive number"); - } + if (maxIterations <= 0) throw new IllegalArgumentException("Max iterations should be a positive number"); + } } diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFm.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFm.java index 7d241d3a79..4694a845af 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFm.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFm.java @@ -19,101 +19,126 @@ import static java.util.stream.Collectors.toSet; public class LastFm { - private static OkHttpClient okHttp = new OkHttpClient.Builder() - .addInterceptor(new LastFmService.Authenticator("put your API key here")) - .build(); + private static OkHttpClient okHttp = new OkHttpClient.Builder() + .addInterceptor(new LastFmService.Authenticator("put your API key here")) + .build(); - private static Retrofit retrofit = new Retrofit.Builder().client(okHttp) - .addConverterFactory(JacksonConverterFactory.create()) - .baseUrl("http://ws.audioscrobbler.com/") - .build(); + private static Retrofit retrofit = new Retrofit.Builder() + .client(okHttp) + .addConverterFactory(JacksonConverterFactory.create()) + .baseUrl("http://ws.audioscrobbler.com/") + .build(); - private static LastFmService lastFm = retrofit.create(LastFmService.class); + private static LastFmService lastFm = retrofit.create(LastFmService.class); - private static ObjectMapper mapper = new ObjectMapper(); + private static ObjectMapper mapper = new ObjectMapper(); - public static void main(String[] args) throws IOException { - List artists = getTop100Artists(); - Set tags = getTop100Tags(); - List records = datasetWithTaggedArtists(artists, tags); + public static void main(String[] args) throws IOException { + List artists = getTop100Artists(); + Set tags = getTop100Tags(); + List records = datasetWithTaggedArtists(artists, tags); - Map> clusters = KMeans.fit(records, 7, new EuclideanDistance(), 1000); - // Print the cluster configuration - clusters.forEach((key, value) -> { - System.out.println("------------------------------ CLUSTER -----------------------------------"); + Map> clusters = KMeans.fit(records, 7, new EuclideanDistance(), 1000); + // Print the cluster configuration + clusters.forEach((key, value) -> { + System.out.println("------------------------------ CLUSTER -----------------------------------"); - System.out.println(sortedCentroid(key)); - String members = String.join(", ", value.stream().map(Record::getDescription).collect(toSet())); - System.out.print(members); + System.out.println(sortedCentroid(key)); + String members = String.join(", ", value + .stream() + .map(Record::getDescription) + .collect(toSet())); + System.out.print(members); - System.out.println(); - System.out.println(); - }); + System.out.println(); + System.out.println(); + }); - Map json = convertToD3CompatibleMap(clusters); - System.out.println(mapper.writeValueAsString(json)); - } + Map json = convertToD3CompatibleMap(clusters); + System.out.println(mapper.writeValueAsString(json)); + } - private static Map convertToD3CompatibleMap(Map> clusters) { - Map json = new HashMap<>(); - json.put("name", "Musicians"); - List> children = new ArrayList<>(); - clusters.forEach((key, value) -> { - Map child = new HashMap<>(); - child.put("name", dominantGenre(sortedCentroid(key))); - List> nested = new ArrayList<>(); - for (Record record : value) { - nested.add(Collections.singletonMap("name", record.getDescription())); - } - child.put("children", nested); + private static Map convertToD3CompatibleMap(Map> clusters) { + Map json = new HashMap<>(); + json.put("name", "Musicians"); + List> children = new ArrayList<>(); + clusters.forEach((key, value) -> { + Map child = new HashMap<>(); + child.put("name", dominantGenre(sortedCentroid(key))); + List> nested = new ArrayList<>(); + for (Record record : value) { + nested.add(Collections.singletonMap("name", record.getDescription())); + } + child.put("children", nested); + children.add(child); + }); + json.put("children", children); + return json; + } - children.add(child); - }); - json.put("children", children); - return json; - } + private static String dominantGenre(Centroid centroid) { + return centroid + .getCoordinates() + .keySet() + .stream() + .limit(2) + .collect(Collectors.joining(", ")); + } - private static String dominantGenre(Centroid centroid) { - return centroid.getCoordinates().keySet().stream().limit(2).collect(Collectors.joining(", ")); - } + private static Centroid sortedCentroid(Centroid key) { + List> entries = new ArrayList<>(key + .getCoordinates() + .entrySet()); + entries.sort((e1, e2) -> e2 + .getValue() + .compareTo(e1.getValue())); - private static Centroid sortedCentroid(Centroid key) { - List> entries = new ArrayList<>(key.getCoordinates().entrySet()); - entries.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue())); + Map sorted = new LinkedHashMap<>(); + for (Map.Entry entry : entries) { + sorted.put(entry.getKey(), entry.getValue()); + } - Map sorted = new LinkedHashMap<>(); - for (Map.Entry entry : entries) { - sorted.put(entry.getKey(), entry.getValue()); - } + return new Centroid(sorted); + } - return new Centroid(sorted); - } + private static List datasetWithTaggedArtists(List artists, Set topTags) throws IOException { + List records = new ArrayList<>(); + for (String artist : artists) { + Map tags = lastFm + .topTagsFor(artist) + .execute() + .body() + .all(); - private static List datasetWithTaggedArtists(List artists, - Set topTags) throws IOException { - List records = new ArrayList<>(); - for (String artist : artists) { - Map tags = lastFm.topTagsFor(artist).execute().body().all(); + // Only keep popular tags. + tags + .entrySet() + .removeIf(e -> !topTags.contains(e.getKey())); - // Only keep popular tags. - tags.entrySet().removeIf(e -> !topTags.contains(e.getKey())); + records.add(new Record(artist, tags)); + } + return records; + } - records.add(new Record(artist, tags)); - } - return records; - } + private static Set getTop100Tags() throws IOException { + return lastFm + .topTags() + .execute() + .body() + .all(); + } - private static Set getTop100Tags() throws IOException { - return lastFm.topTags().execute().body().all(); - } + private static List getTop100Artists() throws IOException { + List artists = new ArrayList<>(); + for (int i = 1; i <= 2; i++) { + artists.addAll(lastFm + .topArtists(i) + .execute() + .body() + .all()); + } - private static List getTop100Artists() throws IOException { - List artists = new ArrayList<>(); - for (int i = 1; i <= 2; i++) { - artists.addAll(lastFm.topArtists(i).execute().body().all()); - } - - return artists; - } + return artists; + } } diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFmService.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFmService.java index cfc8e8d478..db57deb888 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFmService.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/LastFmService.java @@ -23,84 +23,96 @@ import static java.util.stream.Collectors.toList; public interface LastFmService { - @GET("/2.0/?method=chart.gettopartists&format=json&limit=50") - Call topArtists(@Query("page") int page); + @GET("/2.0/?method=chart.gettopartists&format=json&limit=50") + Call topArtists(@Query("page") int page); - @GET("/2.0/?method=artist.gettoptags&format=json&limit=20&autocorrect=1") - Call topTagsFor(@Query("artist") String artist); + @GET("/2.0/?method=artist.gettoptags&format=json&limit=20&autocorrect=1") + Call topTagsFor(@Query("artist") String artist); - @GET("/2.0/?method=chart.gettoptags&format=json&limit=100") - Call topTags(); + @GET("/2.0/?method=chart.gettoptags&format=json&limit=100") + Call topTags(); - /** - * HTTP interceptor to intercept all HTTP requests and add the API key to them. - */ - class Authenticator implements Interceptor { + /** + * HTTP interceptor to intercept all HTTP requests and add the API key to them. + */ + class Authenticator implements Interceptor { - private final String apiKey; + private final String apiKey; - Authenticator(String apiKey) { - this.apiKey = apiKey; - } + Authenticator(String apiKey) { + this.apiKey = apiKey; + } - @Override - public Response intercept(Chain chain) throws IOException { - HttpUrl url = chain.request().url().newBuilder().addQueryParameter("api_key", apiKey).build(); - Request request = chain.request().newBuilder().url(url).build(); + @Override + public Response intercept(Chain chain) throws IOException { + HttpUrl url = chain + .request() + .url() + .newBuilder() + .addQueryParameter("api_key", apiKey) + .build(); + Request request = chain + .request() + .newBuilder() + .url(url) + .build(); - return chain.proceed(request); - } - } + return chain.proceed(request); + } + } - @JsonAutoDetect(fieldVisibility = ANY) - class TopTags { + @JsonAutoDetect(fieldVisibility = ANY) + class TopTags { - private Map tags; + private Map tags; - @SuppressWarnings("unchecked") - public Set all() { - List> topTags = (List>) tags.get("tag"); - return topTags.stream().map(e -> ((String) e.get("name"))).collect(Collectors.toSet()); - } - } + @SuppressWarnings("unchecked") + public Set all() { + List> topTags = (List>) tags.get("tag"); + return topTags + .stream() + .map(e -> ((String) e.get("name"))) + .collect(Collectors.toSet()); + } + } - @JsonAutoDetect(fieldVisibility = ANY) - class Tags { + @JsonAutoDetect(fieldVisibility = ANY) + class Tags { - @JsonProperty("toptags") - private Map topTags; + @JsonProperty("toptags") private Map topTags; - @SuppressWarnings("unchecked") - public Map all() { - try { - Map all = new HashMap<>(); - List> tags = (List>) topTags.get("tag"); - for (Map tag : tags) { - all.put(((String) tag.get("name")), ((Integer) tag.get("count")).doubleValue()); - } + @SuppressWarnings("unchecked") + public Map all() { + try { + Map all = new HashMap<>(); + List> tags = (List>) topTags.get("tag"); + for (Map tag : tags) { + all.put(((String) tag.get("name")), ((Integer) tag.get("count")).doubleValue()); + } - return all; - } - catch (Exception e) { - return Collections.emptyMap(); - } - } - } + return all; + } catch (Exception e) { + return Collections.emptyMap(); + } + } + } - @JsonAutoDetect(fieldVisibility = ANY) - class Artists { + @JsonAutoDetect(fieldVisibility = ANY) + class Artists { - private Map artists; + private Map artists; - @SuppressWarnings("unchecked") - public List all() { - try { - List> artists = (List>) this.artists.get("artist"); - return artists.stream().map(e -> ((String) e.get("name"))).collect(toList()); - } - catch (Exception e) { - return Collections.emptyList(); - } - } - } + @SuppressWarnings("unchecked") + public List all() { + try { + List> artists = (List>) this.artists.get("artist"); + return artists + .stream() + .map(e -> ((String) e.get("name"))) + .collect(toList()); + } catch (Exception e) { + return Collections.emptyList(); + } + } + } } diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java index 6aa2c3ba90..a29e3e054b 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java @@ -9,52 +9,53 @@ import java.util.Objects; */ public class Record { - /** - * The record description. For example, this can be the artist name for the famous musician - * example. - */ - private final String description; + /** + * The record description. For example, this can be the artist name for the famous musician + * example. + */ + private final String description; - /** - * Encapsulates all attributes and their corresponding values, i.e. features. - */ - private final Map features; + /** + * Encapsulates all attributes and their corresponding values, i.e. features. + */ + private final Map features; - public Record(String description, Map features) { - this.description = description; - this.features = features; - } + public Record(String description, Map features) { + this.description = description; + this.features = features; + } - public Record(Map features) { - this("", features); - } + public Record(Map features) { + this("", features); + } - public String getDescription() { - return description; - } + public String getDescription() { + return description; + } - public Map getFeatures() { - return features; - } + public Map getFeatures() { + return features; + } - @Override - public String toString() { - String prefix = description == null || description.trim().isEmpty() ? "Record" : description; + @Override + public String toString() { + String prefix = description == null || description + .trim() + .isEmpty() ? "Record" : description; - return prefix + ": " + features; - } + return prefix + ": " + features; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Record record = (Record) o; - return Objects.equals(getDescription(), record.getDescription()) && - Objects.equals(getFeatures(), record.getFeatures()); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Record record = (Record) o; + return Objects.equals(getDescription(), record.getDescription()) && Objects.equals(getFeatures(), record.getFeatures()); + } - @Override - public int hashCode() { - return Objects.hash(getDescription(), getFeatures()); - } + @Override + public int hashCode() { + return Objects.hash(getDescription(), getFeatures()); + } } diff --git a/algorithms-miscellaneous-3/src/main/resources/kmeans/radial.html b/algorithms-miscellaneous-3/src/main/resources/kmeans/radial.html index d7f9b74cbc..e7e7403871 100644 --- a/algorithms-miscellaneous-3/src/main/resources/kmeans/radial.html +++ b/algorithms-miscellaneous-3/src/main/resources/kmeans/radial.html @@ -6,9 +6,11 @@ stroke: steelblue; stroke-width: 1.5px; } + .node { font: 10px sans-serif; } + .link { fill: none; stroke: #ccc; @@ -21,15 +23,19 @@ var diameter = 1100; var tree = d3.layout.tree() .size([360, diameter / 2 - 300]) - .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; }); + .separation(function (a, b) { + return (a.parent == b.parent ? 1 : 2) / a.depth; + }); var diagonal = d3.svg.diagonal.radial() - .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; }); + .projection(function (d) { + return [d.y, d.x / 180 * Math.PI]; + }); var svg = d3.select("body").append("svg") .attr("width", diameter) .attr("height", diameter - 150) .append("g") .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")"); - d3.json("lastfm.json", function(error, root) { + d3.json("lastfm.json", function (error, root) { var nodes = tree.nodes(root), links = tree.links(nodes); var link = svg.selectAll(".link") @@ -41,14 +47,22 @@ .data(nodes) .enter().append("g") .attr("class", "node") - .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; }) + .attr("transform", function (d) { + return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; + }) node.append("circle") .attr("r", 4.5); node.append("text") .attr("dy", ".31em") - .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; }) - .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; }) - .text(function(d) { return d.name; }); + .attr("text-anchor", function (d) { + return d.x < 180 ? "start" : "end"; + }) + .attr("transform", function (d) { + return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; + }) + .text(function (d) { + return d.name; + }); }); d3.select(self.frameElement).style("height", diameter - 150 + "px"); \ No newline at end of file From 5e7396ba64d1f1ac9c964aad6abc2e1b276cb499 Mon Sep 17 00:00:00 2001 From: Ali Dehghani Date: Wed, 7 Aug 2019 20:53:54 +0430 Subject: [PATCH 5/5] Fixed the single line if statements. --- .../baeldung/algorithms/kmeans/Centroid.java | 8 +++++-- .../algorithms/kmeans/EuclideanDistance.java | 4 +++- .../baeldung/algorithms/kmeans/KMeans.java | 24 ++++++++++++++----- .../baeldung/algorithms/kmeans/Record.java | 8 +++++-- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java index 462936541b..523d5b56a5 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Centroid.java @@ -23,8 +23,12 @@ public class Centroid { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Centroid centroid = (Centroid) o; return Objects.equals(getCoordinates(), centroid.getCoordinates()); } diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java index faccd97599..193d9afed1 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/EuclideanDistance.java @@ -9,7 +9,9 @@ public class EuclideanDistance implements Distance { @Override public double calculate(Map f1, Map f2) { - if (f1 == null || f2 == null) throw new IllegalArgumentException("Feature vectors can't be null"); + if (f1 == null || f2 == null) { + throw new IllegalArgumentException("Feature vectors can't be null"); + } double sum = 0; for (String key : f1.keySet()) { diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java index e1152a67d6..1fb8541ff9 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/KMeans.java @@ -55,7 +55,9 @@ public class KMeans { // if the assignment does not change, then the algorithm terminates boolean shouldTerminate = isLastIteration || clusters.equals(lastState); lastState = clusters; - if (shouldTerminate) break; + if (shouldTerminate) { + break; + } // at the end of each iteration we should relocate the centroids centroids = relocateCentroids(clusters); @@ -92,7 +94,9 @@ public class KMeans { */ private static Centroid average(Centroid centroid, List records) { // if this cluster is empty, then we shouldn't move the centroid - if (records == null || records.isEmpty()) return centroid; + if (records == null || records.isEmpty()) { + return centroid; + } // Since some records don't have all possible attributes, we initialize // average coordinates equal to current centroid coordinates @@ -213,12 +217,20 @@ public class KMeans { } private static void applyPreconditions(List records, int k, Distance distance, int maxIterations) { - if (records == null || records.isEmpty()) throw new IllegalArgumentException("The dataset can't be empty"); + if (records == null || records.isEmpty()) { + throw new IllegalArgumentException("The dataset can't be empty"); + } - if (k <= 1) throw new IllegalArgumentException("It doesn't make sense to have less than or equal to 1 cluster"); + if (k <= 1) { + throw new IllegalArgumentException("It doesn't make sense to have less than or equal to 1 cluster"); + } - if (distance == null) throw new IllegalArgumentException("The distance calculator is required"); + if (distance == null) { + throw new IllegalArgumentException("The distance calculator is required"); + } - if (maxIterations <= 0) throw new IllegalArgumentException("Max iterations should be a positive number"); + if (maxIterations <= 0) { + throw new IllegalArgumentException("Max iterations should be a positive number"); + } } } diff --git a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java index a29e3e054b..d2a7b61c62 100644 --- a/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java +++ b/algorithms-miscellaneous-3/src/main/java/com/baeldung/algorithms/kmeans/Record.java @@ -48,8 +48,12 @@ public class Record { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Record record = (Record) o; return Objects.equals(getDescription(), record.getDescription()) && Objects.equals(getFeatures(), record.getFeatures()); }