def plot_features(self, plot_kind="3D", dim=None, row_size=5, show=True): """ This function plot the features vector of all candidates in the given dimensions. Parameters ---------- plot_kind : str The kind of plot we want to show. Can be ``'3D'`` or ``'ternary'``. dim : list The 3 dimensions we are using for our plot. By default, it is set to ``[0, 1, 2]``. row_size : int The number of subplots by row. By default, it is set to 5 plots by row. show : bool If True, plot the figure at the end of the function. """ if dim is None: dim = [0, 1, 2] else: if len(dim) != 3: raise ValueError("The number of dimensions should be 3") n_candidates = self.ratings_.n_candidates n_rows = (n_candidates - 1) // row_size + 1 fig = plt.figure(figsize=(row_size * 5, n_rows * 5)) plot_position = [n_rows, row_size, 1] features = self.features_ for candidate in range(n_candidates): ax = self.embeddings_.plot_candidate(self.ratings_, candidate, plot_kind=plot_kind, dim=dim, fig=fig, plot_position=plot_position, show=False) if plot_kind == "3D": x1 = features[candidate, dim[0]] x2 = features[candidate, dim[1]] x3 = features[candidate, dim[2]] ax.plot([0, x1], [0, x2], [0, x3], color='k', linewidth=2) ax.scatter([x1], [x2], [x3], color='k', s=5) elif plot_kind == "ternary": x1 = features[candidate, dim[0]] x2 = features[candidate, dim[2]] x3 = features[candidate, dim[1]] feature_bis = [x1, x2, x3] feature_bis = np.maximum(feature_bis, 0) size_features = np.linalg.norm(feature_bis) feature_bis = normalize(feature_bis) ax.scatter([feature_bis**2], color='k', s=50 * size_features + 1) plot_position[2] += 1 if show: plt.show() # pragma: no cover
def _plot_ternary(self, fig, dim, plot_position=None): """ Plot a figure of the ratings on a 2D space representing the surface of the unit sphere on the non-negative orthant. Parameters ---------- fig : matplotlib figure The figure on which we add the plot. dim : list The 3 dimensions we are using for our plot. plot_position : list The position of the plot on the figure. Should be of the form ``[n_rows, n_columns, position]``. Return ------ matplotlib ax The matplotlib ax with the figure, if you want to add something to it. """ tax = create_ternary_plot(fig, plot_position) for i, v in enumerate(self): x1 = v[dim[0]] x2 = v[dim[2]] x3 = v[dim[1]] vec = [x1, x2, x3] tax.scatter([normalize(vec)**2], color=(x1**2 * 0.8, x3**2 * 0.8, x2**2 * 0.8), alpha=0.9, s=30) return tax
def _get_center(self): """ Return the center of the ratings, computed as the center of the :attr:`n_dim`-dimensional cube of maximal volume. Return ------ np.ndarray The position of the center vector. Should be of length :attr:`n_dim`. """ positions = np.array(self) matrix_rank = np.linalg.matrix_rank(positions) volume = 0 n_voters = self.n_voters current_subset = list(np.arange(matrix_rank)) mean = np.zeros(self.n_dim) while current_subset[0] <= n_voters - matrix_rank: current_embeddings = positions[current_subset, ...] new_volume = np.sqrt(np.linalg.det(np.dot(current_embeddings, current_embeddings.T))) if new_volume > volume: volume = new_volume mean = normalize(current_embeddings.sum(axis=0)) x = 1 while current_subset[matrix_rank - x] == n_voters - x: x += 1 val = current_subset[matrix_rank - x] + 1 while x > 0: current_subset[matrix_rank - x] = val val += 1 x -= 1 return mean
def __call__(self, polarisation=0.0): """ Update the parameter of the parametric embeddings and create a new ratings. Parameters _________ polarisation : float Should be between `0` and `1`. If it is equal to `0`, then the embeddings are uniformly distributed. If it is equal to `1`, then each voter's embeddings align to the dimension of its group. Return ------ Embeddings The embeddings generated Examples -------- >>> np.random.seed(42) >>> generator = EmbeddingsGeneratorPolarized(100, 3) >>> embs = generator(.8) >>> embs.voter_embeddings(0) array([0.12915167, 0.03595039, 0.99097296]) """ if polarisation > 1 or polarisation < 0: raise ValueError("Polarisation should be between 0 and 1") n = len(self._thetas) positions = np.zeros((n, self.n_dim)) for i in range(n): p_1 = np.dot(self._orthogonal_profile[i], self._random_profile[i]) * self._orthogonal_profile[i] p_2 = self._random_profile[i] - p_1 e_2 = normalize(p_2) positions[i] = self._orthogonal_profile[i] * np.cos( self._thetas[i] * (1 - polarisation)) + e_2 * np.sin(self._thetas[i] * (1 - polarisation)) return Embeddings(positions)
def plot_scores_evolution(self, show=True): """ This function plot the evolution of the scores of the candidates when the moving voters' embeddings are changing. Parameters ---------- show : bool If True, displays the figure at the end of the function. Examples -------- >>> p = MovingVoter()(SVDNash()) >>> p.plot_scores_evolution(show=False) """ tab_x = np.linspace(0, 1, 50) tab_y = [] for x in tab_x: self.embeddings[self.moving_voter] = normalize([1 - x, x, 0]) tab_y.append( self.rule(self.ratings_, self.embeddings).scores_float_) tab_y = np.array(tab_y).T name = ["Start", "End", "Orthogonal", "Consensus"] for i in range(self.ratings_.n_candidates): plt.plot(tab_x, tab_y[i], label=name[i]) plt.title("Evolution of the score") plt.xlabel("X coordinate of moving voter") plt.ylabel("Score") plt.xlim(0, 1) plt.legend() if show: plt.show() # pragma: no cover
def _build_profiles(self): """ This function build the two embeddings of the parametrized embeddings (uniform and orthogonal). Return ------ EmbeddingsGeneratorPolarized The object itself. """ for i in range(self.n_voters): new_vec = np.abs(np.random.randn(self.n_dim)) r = np.argmax(new_vec * self.prob) new_vec = normalize(new_vec) self._orthogonal_profile[i, r] = 1 self._random_profile[i] = new_vec theta = np.arccos( np.dot(self._random_profile[i], self._orthogonal_profile[i])) self._thetas[i] = theta return self
def _plot_scores_ternary(self, sizes, fig, plot_position, dim): """ Plot a figure of the ratings on a 2D space representing the sphere in the non-negative orthant, with the voters dots having the sizes passed as parameters. Parameters ---------- sizes : np.ndarray The size of the dots. Should be of length :attr:`n_voters`. fig : matplotlib figure The figure on which we add the plot. plot_position : list The position of the plot on the figure. Should be of the form ``[n_rows, n_columns, position]``. dim : list The 3 dimensions we are using for our plot. Return ------ matplotlib ax The matplotlib ax with the figure, if you want to add something to it. """ tax = create_ternary_plot(fig, plot_position) for i, (v, s) in enumerate(zip(np.array(self), sizes)): x1 = v[dim[0]] x2 = v[dim[1]] x3 = v[dim[2]] vec = [x1, x2, x3] tax.scatter([normalize(vec)**2], color=(x1**2 * 0.8, x3**2 * 0.8, x2**2 * 0.8), alpha=0.7, s=max(s * 50, 1)) return tax
def plot_features_evolution(self, show=True): """ This function plot the evolution of the features of the candidates when the moving voters' embeddings are changing. Only works for :class:`SVDMax` and :class:`FeaturesRule`. Parameters ---------- show : bool If True, displays the figure at the end of the function. Examples -------- >>> p = MovingVoter()(SVDMax()) >>> p.plot_features_evolution(show=False) """ tab_x = np.linspace(0, 1, 20) tab_y = [] for x in tab_x: self.embeddings[self.moving_voter] = normalize([1 - x, x, 0]) tab_y.append(self.rule(self.ratings_, self.embeddings).features_) tab_y = np.array(tab_y) fig = plt.figure(figsize=(10, 5)) ax = create_3D_plot(fig, position=[1, 2, 1]) name = ["Start", "End", "Orthogonal", "Consensus"] for i in range(self.ratings_.n_candidates): vec_init = normalize(tab_y[0, i])**2 ax.plot(tab_y[::, i, 0], tab_y[::, i, 1], tab_y[::, i, 2], color=(vec_init[0] * 0.8, vec_init[1] * 0.8, vec_init[2] * 0.8), alpha=0.5, label=name[i]) for j, v in enumerate(tab_y[::, i]): vec_normalized = normalize(v) ax.plot([0, v[0]], [0, v[1]], [0, v[2]], color=(vec_normalized[0] * 0.8, vec_normalized[1] * 0.8, vec_normalized[2] * 0.8), alpha=j / 60) ax.set_title("Evolution of the features") plt.legend() tax = create_ternary_plot(fig, position=[1, 2, 2]) for i in range(self.ratings_.n_candidates): points = [normalize(x[[0, 2, 1]])**2 for x in tab_y[::, i]] vec_init = normalize(tab_y[0, i, [0, 2, 1]])**2 tax.plot(points, color=(vec_init[0] * 0.8, vec_init[2] * 0.8, vec_init[1] * 0.8), alpha=0.8) tax.scatter([vec_init], color=(vec_init[0] * 0.8, vec_init[2] * 0.8, vec_init[1] * 0.8), alpha=0.7, s=50) if show: plt.show() # pragma: no cover
def plot_weights(self, plot_kind="3D", dim=None, row_size=5, verbose=True, show=True): """ This function plot the evolution of the voters' weights after each step of the rule. Parameters ---------- plot_kind : str The kind of plot we want to show. Can be ``3D`` or ``ternary``. dim : list The 3 dimensions we are using for our plot. By default, it is set to ``[0, 1, 2]``. row_size : int Number of subplots by row. By default, it is set to 5. verbose : bool If True, print the total weight divided by the number of remaining candidates at the end of each step. show : bool If True, displays the figure at the end of the function. """ ls_weight = self._ruleResults["weights_list"] vectors = self._ruleResults["vectors"] n_candidates = len(ls_weight) n_rows = (n_candidates - 1) // row_size + 1 fig = plt.figure(figsize=(5 * row_size, n_rows * 5)) plot_position = [n_rows, row_size, 1] if dim is None: dim = [0, 1, 2] for i in range(n_candidates): ax = self.embeddings.plot_scores(ls_weight[i], plot_kind=plot_kind, title="Step %i" % i, dim=dim, fig=fig, plot_position=plot_position, show=False) if i < n_candidates - 1: x1 = vectors[i][dim[0]] x2 = vectors[i][dim[1]] x3 = vectors[i][dim[2]] if plot_kind == "3D": ax.plot([0, x1], [0, x2], [0, x3], color='k', linewidth=2) ax.scatter([x1], [x2], [x3], color='k', s=5) elif plot_kind == "ternary": feature_bis = [x1, x3, x2] feature_bis = np.maximum(feature_bis, 0) size_features = np.linalg.norm(feature_bis) feature_bis = normalize(feature_bis) ax.scatter([feature_bis ** 2], color='k', s=50*size_features+1) plot_position[2] += 1 if verbose: sum_w = [ls_weight[i].sum() / (n_candidates - i - 1) for i in range(n_candidates - 1)] print("Weight / remaining candidate : ", sum_w) if show: plt.show() # pragma: no cover
def recenter(self, approx=True): """ Recenter the embeddings of the voters so that they are the most possible on the non-negative orthant. Parameters ---------- approx : bool If True, we compute the center of the population with a polynomial time algorithm. If False, we use an algorithm exponential in :attr:`n_dim`. Return ------ Embeddings The embeddings itself. Examples -------- >>> embs = Embeddings(-np.array([[.5,.9,.4],[.4,.7,.5],[.4,.2,.4]])).normalize() >>> embs Embeddings([[-0.45267873, -0.81482171, -0.36214298], [-0.42163702, -0.73786479, -0.52704628], [-0.66666667, -0.33333333, -0.66666667]]) >>> embs.recenter() Embeddings([[0.40215359, 0.75125134, 0.52334875], [0.56352875, 0.6747875 , 0.47654713], [0.70288844, 0.24253193, 0.66867489]]) """ if self.n_voters < 2: raise ValueError("Cannot recenter a ratings with less than 2 candidates") positions = np.array(self) if approx: center = normalize(positions.sum(axis=0)) else: center = self._get_center() target_center = np.ones(self.n_dim) target_center = normalize(target_center) if np.dot(center, target_center) == -1: new_positions = - self elif np.dot(center, target_center) == 1: new_positions = self else: orthogonal_center = center - np.dot(center, target_center)*target_center orthogonal_center = normalize(orthogonal_center) theta = -np.arccos(np.dot(center, target_center)) rotation_matrix = np.array([[np.cos(theta), - np.sin(theta)], [np.sin(theta), np.cos(theta)]]) new_positions = np.zeros((self.n_voters, self.n_dim)) for i in range(self.n_voters): position_i = self.voter_embeddings(i) comp_1 = position_i.dot(target_center) comp_2 = position_i.dot(orthogonal_center) vector = [comp_1, comp_2] remainder = position_i - comp_1*target_center - comp_2*orthogonal_center new_vector = rotation_matrix.dot(vector) new_positions[i] = new_vector[0]*target_center + new_vector[1]*orthogonal_center + remainder return Embeddings(new_positions, False)
def dilate(self, approx=True): """ Dilate the embeddings of the voters so that they take all the space possible in the non-negative orthant. Parameters ---------- approx : bool If True, we compute the center of the population with a polynomial time algorithm. If False, we use an algorithm exponential in :attr:`n_dim`. Return ------ Embeddings The embeddings itself. Examples -------- >>> embs = Embeddings(np.array([[.5,.4,.4],[.4,.4,.5],[.4,.5,.4]])).normalize() >>> embs Embeddings([[0.66226618, 0.52981294, 0.52981294], [0.52981294, 0.52981294, 0.66226618], [0.52981294, 0.66226618, 0.52981294]]) >>> embs.dilate() Embeddings([[0.98559856, 0.11957316, 0.11957316], [0.11957316, 0.11957316, 0.98559856], [0.11957316, 0.98559856, 0.11957316]]) """ if self.n_voters < 2: raise ValueError("Cannot dilate a ratings with less than 2 candidates") positions = np.array(self) if approx: center = normalize(positions.sum(axis=0)) else: center = self._get_center() min_value = np.dot(self.voter_embeddings(0), center) for i in range(self.n_voters): val = np.dot(self.voter_embeddings(i), center) if val < min_value: min_value = val theta_max = np.arccos(min_value) k = np.pi / (4 * theta_max) new_positions = np.zeros((self.n_voters, self.n_dim)) for i in range(self.n_voters): v_i = self.voter_embeddings(i) theta_i = np.arccos(np.dot(v_i, center)) if theta_i == 0: new_positions[i] = v_i else: p_1 = np.dot(center, v_i) * center p_2 = v_i - p_1 e_2 = normalize(p_2) new_positions[i] = center * np.cos(k * theta_i) + e_2 * np.sin(k * theta_i) return Embeddings(new_positions, False)