community_louvain.m 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. function [M,Q]=community_louvain(W,gamma,M0,B)
  2. %COMMUNITY_LOUVAIN Optimal community structure
  3. %
  4. % M = community_louvain(W);
  5. % [M,Q] = community_louvain(W,gamma);
  6. % [M,Q] = community_louvain(W,gamma,M0);
  7. % [M,Q] = community_louvain(W,gamma,M0,'potts');
  8. % [M,Q] = community_louvain(W,gamma,M0,'negative_asym');
  9. % [M,Q] = community_louvain(W,[],[],B);
  10. %
  11. % The optimal community structure is a subdivision of the network into
  12. % nonoverlapping groups of nodes which maximizes the number of within-
  13. % group edges, and minimizes the number of between-group edges.
  14. %
  15. % This function is a fast and accurate multi-iterative generalization of
  16. % the Louvain community detection algorithm. This function subsumes and
  17. % improves upon,
  18. % modularity_louvain_und.m, modularity_finetune_und.m,
  19. % modularity_louvain_dir.m, modularity_finetune_dir.m,
  20. % modularity_louvain_und_sign.m
  21. % and additionally allows to optimize other objective functions (includes
  22. % built-in Potts-model Hamiltonian, allows for custom objective-function
  23. % matrices).
  24. %
  25. % Inputs:
  26. % W,
  27. % directed/undirected weighted/binary connection matrix with
  28. % positive and possibly negative weights.
  29. % gamma,
  30. % resolution parameter (optional)
  31. % gamma>1, detects smaller modules
  32. % 0<=gamma<1, detects larger modules
  33. % gamma=1, classic modularity (default)
  34. % M0,
  35. % initial community affiliation vector (optional)
  36. % B,
  37. % objective-function type or custom objective matrix (optional)
  38. % 'modularity', modularity (default)
  39. % 'potts', Potts-model Hamiltonian (for binary networks)
  40. % 'negative_sym', symmetric treatment of negative weights
  41. % 'negative_asym', asymmetric treatment of negative weights
  42. % B, custom objective-function matrix
  43. %
  44. % Note: see Rubinov and Sporns (2011) for a discussion of
  45. % symmetric vs. asymmetric treatment of negative weights.
  46. %
  47. % Outputs:
  48. % M,
  49. % community affiliation vector
  50. % Q,
  51. % optimized community-structure statistic (modularity by default)
  52. %
  53. % Example:
  54. % % Iterative community finetuning.
  55. % % W is the input connection matrix.
  56. % n = size(W,1); % number of nodes
  57. % M = 1:n; % initial community affiliations
  58. % Q0 = -1; Q1 = 0; % initialize modularity values
  59. % while Q1-Q0>1e-5; % while modularity increases
  60. % Q0 = Q1; % perform community detection
  61. % [M, Q1] = community_louvain(W, [], M);
  62. % end
  63. %
  64. % References:
  65. % Blondel et al. (2008) J. Stat. Mech. P10008.
  66. % Reichardt and Bornholdt (2006) Phys. Rev. E 74, 016110.
  67. % Ronhovde and Nussinov (2008) Phys. Rev. E 80, 016109
  68. % Sun et al. (2008) Europhysics Lett 86, 28004.
  69. % Rubinov and Sporns (2011) Neuroimage 56:2068-79.
  70. %
  71. % Mika Rubinov, U Cambridge 2015-2016
  72. % Modification history
  73. % 2015: Original
  74. % 2016: Included generalization for negative weights.
  75. % Enforced binary network input for Potts-model Hamiltonian.
  76. % Streamlined code and expanded documentation.
  77. W=double(W); % convert to double format
  78. n=length(W); % get number of nodes
  79. s=sum(sum(W)); % get sum of edges
  80. if ~exist('B','var') || isempty(B)
  81. type_B = 'modularity';
  82. elseif ischar(B)
  83. type_B = B;
  84. else
  85. type_B = 0;
  86. if exist('gamma','var') && ~isempty(gamma)
  87. warning('Value of gamma is ignored in generalized mode.')
  88. end
  89. end
  90. if ~exist('gamma','var') || isempty(gamma)
  91. gamma = 1;
  92. end
  93. if strcmp(type_B,'negative_sym') || strcmp(type_B,'negative_asym')
  94. W0 = W.*(W>0); %positive weights matrix
  95. s0 = sum(sum(W0)); %weight of positive links
  96. B0 = W0-gamma*(sum(W0,2)*sum(W0,1))/s0; %positive modularity
  97. W1 =-W.*(W<0); %negative weights matrix
  98. s1 = sum(sum(W1)); %weight of negative links
  99. if s1 %negative modularity
  100. B1 = W1-gamma*(sum(W1,2)*sum(W1,1))/s1;
  101. else
  102. B1 = 0;
  103. end
  104. elseif min(min(W))<-1e-10
  105. err_string = [
  106. 'The input connection matrix contains negative weights.\nSpecify ' ...
  107. '''negative_sym'' or ''negative_asym'' objective-function types.'];
  108. error(sprintf(err_string)) %#ok<SPERR>
  109. end
  110. if strcmp(type_B,'potts') && any(any(W ~= logical(W)))
  111. error('Potts-model Hamiltonian requires a binary W.')
  112. end
  113. if type_B
  114. switch type_B
  115. case 'modularity'; B = (W-gamma*(sum(W,2)*sum(W,1))/s)/s;
  116. case 'potts'; B = W-gamma*(~W);
  117. case 'negative_sym'; B = B0/(s0+s1) - B1/(s0+s1);
  118. case 'negative_asym'; B = B0/s0 - B1/(s0+s1);
  119. otherwise; error('Unknown objective function.');
  120. end
  121. else % custom objective function matrix as input
  122. B = double(B);
  123. if ~isequal(size(W),size(B))
  124. error('W and B must have the same size.')
  125. end
  126. end
  127. if ~exist('M0','var') || isempty(M0)
  128. M0=1:n;
  129. elseif numel(M0)~=n
  130. error('M0 must contain n elements.')
  131. end
  132. [~,~,Mb] = unique(M0);
  133. M = Mb;
  134. B = (B+B.')/2; % symmetrize modularity matrix
  135. Hnm=zeros(n,n); % node-to-module degree
  136. for m=1:max(Mb) % loop over modules
  137. Hnm(:,m)=sum(B(:,Mb==m),2);
  138. end
  139. Q0 = -inf;
  140. Q = sum(B(bsxfun(@eq,M0,M0.'))); % compute modularity
  141. first_iteration = true;
  142. while Q-Q0>1e-10
  143. flag = true; % flag for within-hierarchy search
  144. while flag
  145. flag = false;
  146. for u=randperm(n) % loop over all nodes in random order
  147. ma = Mb(u); % current module of u
  148. dQ = Hnm(u,:) - Hnm(u,ma) + B(u,u);
  149. dQ(ma) = 0; % (line above) algorithm condition
  150. [max_dQ,mb] = max(dQ); % maximal increase in modularity and corresponding module
  151. if max_dQ>1e-10 % if maximal increase is positive
  152. flag = true;
  153. Mb(u) = mb; % reassign module
  154. Hnm(:,mb) = Hnm(:,mb)+B(:,u); % change node-to-module strengths
  155. Hnm(:,ma) = Hnm(:,ma)-B(:,u);
  156. end
  157. end
  158. end
  159. [~,~,Mb] = unique(Mb); % new module assignments
  160. M0 = M;
  161. if first_iteration
  162. M=Mb;
  163. first_iteration=false;
  164. else
  165. for u=1:n % loop through initial module assignments
  166. M(M0==u)=Mb(u); % assign new modules
  167. end
  168. end
  169. n=max(Mb); % new number of modules
  170. B1=zeros(n); % new weighted matrix
  171. for u=1:n
  172. for v=u:n
  173. bm=sum(sum(B(Mb==u,Mb==v))); % pool weights of nodes in same module
  174. B1(u,v)=bm;
  175. B1(v,u)=bm;
  176. end
  177. end
  178. B=B1;
  179. Mb=1:n; % initial module assignments
  180. Hnm=B; % node-to-module strength
  181. Q0=Q;
  182. Q=trace(B); % compute modularity
  183. end