From 5988b6a00d3266465273a7c6e3f04e0affb69a88 Mon Sep 17 00:00:00 2001
From: Ibrahim Abou Elseoud <Ibrahim.elseoud@gmail.com>
Date: Mon, 10 Apr 2017 19:25:57 +0200
Subject: [PATCH 1/7] Implemented incremental SVD ;however, can read only 1
 tuple from file and update each time

---
 recsys/__init__.pyc              | Bin 0 -> 556 bytes
 recsys/algorithm/__init__.pyc    | Bin 0 -> 251 bytes
 recsys/algorithm/baseclass.py    |   6 ++
 recsys/algorithm/baseclass.pyc   | Bin 0 -> 11549 bytes
 recsys/algorithm/factorize.py    | 126 ++++++++++++++++++++++++++++++-
 recsys/algorithm/factorize.pyc   | Bin 0 -> 25820 bytes
 recsys/algorithm/matrix.py       |  28 +++++--
 recsys/algorithm/matrix.pyc      | Bin 0 -> 7182 bytes
 recsys/datamodel/__init__.pyc    | Bin 0 -> 226 bytes
 recsys/datamodel/data.pyc        | Bin 0 -> 8343 bytes
 recsys/evaluation/__init__.pyc   | Bin 0 -> 269 bytes
 recsys/evaluation/baseclass.pyc  | Bin 0 -> 4781 bytes
 recsys/evaluation/prediction.pyc | Bin 0 -> 3802 bytes
 13 files changed, 154 insertions(+), 6 deletions(-)
 create mode 100644 recsys/__init__.pyc
 create mode 100644 recsys/algorithm/__init__.pyc
 create mode 100644 recsys/algorithm/baseclass.pyc
 create mode 100644 recsys/algorithm/factorize.pyc
 create mode 100644 recsys/algorithm/matrix.pyc
 create mode 100644 recsys/datamodel/__init__.pyc
 create mode 100644 recsys/datamodel/data.pyc
 create mode 100644 recsys/evaluation/__init__.pyc
 create mode 100644 recsys/evaluation/baseclass.pyc
 create mode 100644 recsys/evaluation/prediction.pyc

diff --git a/recsys/__init__.pyc b/recsys/__init__.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a6bc57bd3ac78b000f3d61b363cfbd46fbc14d5c
GIT binary patch
literal 556
zcmY*VO;6k~5S?W|SeCZxPsnjEY@o-gswkD{0R$^?LAhAQ9&KFNF|sE}`*ZmX`~jX5
z1R{xN-kb58%*^!V>ne25_lDk{8T>g;zJ#$v4eW%P3E2Z`4#-ZanUbARGaDjvvU6${
zWEa#NlEt?qdqj3cJKO<2rn~hQl#&HZVw3kdp8-DZrH$Rm82MTKsj>@?o~tolAs$2E
z&T^_C5f1qns@rUIs{XPMI#0n*H$SBKf7|Q)9X^_pI^PBD{00@IjB~-F<p@CgT})2e
zFfgk{hiKW$3ORn#zSrVnCv(tVoX<DwqLL+yQk@R0lpGGkMgv@QO6eYxB+$~JQcs*R
zs=RaW40pLmzYTe`)(H+5NvW?Cmv+I%dKumJ8Fc)W_DHR9k3LMk0<GH^xONXow=UG%
zy=ip{+Z6w^kz8MB)3T4L1nN)AuE(9%O1aL7QX6E;h`+Fm-t+GX@UYCq<n#Lq)B%nB

literal 0
HcmV?d00001

diff --git a/recsys/algorithm/__init__.pyc b/recsys/algorithm/__init__.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..89220e34927d884b6c3dce3b3a58b9984843c127
GIT binary patch
literal 251
zcmYLEO9}!p49(2o7u<RVH`)t`$bc?Y6h+*W+Nsk@JC!zq<83{I7chRnKwe(n3n9s#
zy{@YsdkMctsZJ?lpFjZ|5-0$Q1j-Z+DHJJe1{qn{Bya+QxnYOsNl5g;+|}Z1H%OZc
z8x^*Q?PlwIxtV=9=fYaf2PDHE;+9&by;IB_gV4sYRV}p$ZWa7lNsKHPQY-7jTVR(O
pwXcRj$yi7BE5ep^18T=OH<dxoM>N-_&12__t5uVPczplZOkOKXJ&^za

literal 0
HcmV?d00001

diff --git a/recsys/algorithm/baseclass.py b/recsys/algorithm/baseclass.py
index 89656a5..681b805 100644
--- a/recsys/algorithm/baseclass.py
+++ b/recsys/algorithm/baseclass.py
@@ -36,6 +36,10 @@ def __init__(self):
         self._matrix_similarity = None #self-similarity matrix (only for the input Matrix rows)
         self._matrix_and_data_aligned = False #both Matrix and Data contain the same info?
 
+       #new for update
+        self._updateData=Data()
+        self._singleUpdateMatrix=SparseMatrix()
+
     def __len__(self):
         return len(self.get_data())
 
@@ -97,6 +101,8 @@ def load_data(self, filename, force=True, sep='\t', format={'value':0, 'row':1,
             self._matrix_similarity = None
 
         self._data.load(filename, force, sep, format, pickle)
+
+
     
     def save_data(self, filename, pickle=False):
         """
diff --git a/recsys/algorithm/baseclass.pyc b/recsys/algorithm/baseclass.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..16b11748948af3db710aff504e77c4d4593df659
GIT binary patch
literal 11549
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

literal 0
HcmV?d00001

diff --git a/recsys/algorithm/factorize.py b/recsys/algorithm/factorize.py
index 00a9e61..6d41edb 100644
--- a/recsys/algorithm/factorize.py
+++ b/recsys/algorithm/factorize.py
@@ -9,6 +9,7 @@
 import os
 import sys
 import zipfile
+
 try:
     import divisi2
 except:
@@ -30,6 +31,10 @@
 from recsys.algorithm.matrix import SimilarityMatrix
 from recsys.algorithm import VERBOSE
 
+from numpy.linalg import inv #for update
+import numpy as np
+from recsys.datamodel.data import Data
+
 TMPDIR = '/tmp'
 
 class SVD(Algorithm):
@@ -66,6 +71,9 @@ def __init__(self, filename=None):
         self._file_row_ids = None
         self._file_col_ids = None
 
+        #Update feature
+
+
     def __repr__(self):
         try:
             s = '\n'.join(('M\':' + str(self._reconstruct_matrix()), \
@@ -241,7 +249,7 @@ def compute(self, k=100, min_values=None, pre_normalize=None, mean_center=False,
         :param savefile: path to save the SVD factorization (U, Sigma and V matrices)
         :type savefile: string
         """
-        super(SVD, self).compute(min_values)
+        super(SVD, self).compute(min_values) #creates matrix and does squish to not have empty values
 
         if VERBOSE:
             sys.stdout.write('Computing svd k=%s, min_values=%s, pre_normalize=%s, mean_center=%s, post_normalize=%s\n' 
@@ -352,6 +360,122 @@ def recommend(self, i, n=10, only_unknowns=False, is_row=True):
             item = self._get_col_reconstructed(i, zeros)
         return item.top_items(n)
 
+    def load_updateDataTuple(self, filename, force=True, sep='\t', format={'value':0, 'row':1, 'col':2}, pickle=False,is_row=True):
+        """
+        Loads a dataset file that contains a tuple
+
+        See params definition in *datamodel.Data.load()*
+        """
+        # nDimension
+        if force:
+            self._updateData = Data()
+
+        self._updateData.load(filename, force, sep, format, pickle)
+        print "reading the new tuple"
+        if(is_row):
+            nDimensionLabels=self._V.all_labels()
+            # print nDimensionLabels
+            self._singleUpdateMatrix.create(self._updateData.get(),col_labels=nDimensionLabels[0])
+
+        else:
+            nDimensionLabels = self._U.all_labels()
+            # print nDimensionLabels
+            self._singleUpdateMatrix.create(self._updateData.get(), row_labels=nDimensionLabels[0])
+
+        # #update the data matrix
+        print "updating the sparse matrix"
+        # print "matrix before update:",self._matrix.get().shape
+        self._matrix.update(self._singleUpdateMatrix) # updating the data matrix for the zeroes , also for saving the data matrix if needed
+        # print "matrix after update:",self._matrix.get().shape
+
+    def update_sparse_matrix_data(self,squishFactor=10):
+        #update the data matrix
+        # print "matrix before update:",self._matrix.get().shape
+        print "commiting the sparse data matrix by removing empty rows and columns divisi created"
+        self._matrix.squish(squishFactor) # updating the data matrix for the zeroes ,#NOTE: Intensive so do at end
+        # print "matrix after update:",self._matrix.get().shape
+
+    def update(self,is_row=True): #update(tuple:denseVector tuple,isRow=True,,
+      print "type of S",type(self._S)
+      print "type of U",type(self._U)
+      print "type of V",type(self._V)
+      print "type of data",type(self._data)
+      print "type of matrix",type(self._matrix)
+      print "type of matrix reconstructed",type(self._matrix_reconstructed)
+      print "type of matrix similarity",type(self._matrix_similarity)
+
+      print "dimensions of S",self._S.shape
+      print "dimensions of U",self._U.shape
+      print "dimensions of V",self._V.shape
+
+
+      invS=np.zeros((self._S.shape[0], self._S.shape[0]))
+      for i in range(self._S.shape[0]):
+          invS[i, i] = self._S[i]**-1  # creating diagonal matrix and inverting using special property of diagonal matrix
+
+      #if new is row -> V*S^-1
+      if is_row:
+        prodM=self._V.dot(invS)
+        print "dimension of VxS^-1=", prodM.shape
+      else:       #if new is col -> U*S^-1
+        prodM = self._U.dot(invS)
+        print "dimension of UxS^-1=", prodM.shape
+
+      updateTupleMatrix=self._singleUpdateMatrix.get()
+      if not is_row:
+          updateTupleMatrix=updateTupleMatrix.transpose() #transpose
+      print "dimensions of user",updateTupleMatrix.shape
+      res=updateTupleMatrix.dot(prodM)
+      print "type of res=", type(res)
+      print "dimension of resultant is", res.shape
+
+      if is_row:
+      #use new value can now be concatinated with U
+        print "U before adding", self._U.shape
+        self._U=self._U.concatenate(res)
+        print "U after adding", self._U.shape
+
+      else:
+        print "V before adding", self._V.shape
+        self._V = self._V.concatenate(res)
+        print "V after adding", self._V.shape
+
+      print "before updating, M=",self._matrix_reconstructed.shape
+      # Sim. matrix = U \Sigma^2 U^T
+      self._reconstruct_similarity(post_normalize=False, force=True)
+      # M' = U S V^t
+      self._reconstruct_matrix(shifts=self._shifts, force=True)
+
+      print "done updating, M=",self._matrix_reconstructed.shape
+
+
+
+
+      # myFile=open("prodMVSq.dat",'w')
+      # myFile.truncate()
+      #
+      # for i in range(20):
+      #   myFile.write(str(res[0, i])+" ")
+      #
+      #   myFile.write("\n")
+
+      # # invS = inv(diag_S)
+      # # print "dimensions of S^-1", invS.shape
+      #
+      #
+      # print "writing s to file"
+      # myFile=open("invS.dat",'w')
+      # myFile.truncate()
+      # # for item in self.invS.tolist():
+      # #     myFile.write(str(item))
+      # #     myFile.write("\n")
+      # myFile.write("dimensions= "+str(invS.shape))
+      # myFile.write("\n")
+      # for i in range(invS.shape[0]):
+      #   myFile.write(str(invS[i,i]))
+      #   myFile.write("\n")
+
+
     def centroid(self, ids, is_row=True):
         points = []
         for id in ids:
diff --git a/recsys/algorithm/factorize.pyc b/recsys/algorithm/factorize.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8ddc6846c4d74c0e30b323e23e17fcfaff1d0441
GIT binary patch
literal 25820
zcmeHwU2t62ec!pe_*^VLh%XW%C0<&Rv7pGMNXN1bk&;M)q8JMhb3uYOL~6C%y#Ow-
zU%2Nk35bO2hH^4_NivhP(@Zi=W|B!-x07VX51l?WPMjvwx|6h%wwX*EXPUgUe&}@4
zw?4SP-~XJudv^&+wiCk5Od;O0_k5r8Kj-|v&sF+whBNEUzy3l=*}pV?-@v1P(^0Cd
z)G9(pl~bzXs8zg}_mo;q#qVjgnpV4GakNLR_NZI6!^&|SmQJfmuc}I_Q*+c8nAW2z
zeX828R{K?TK&=j_>Y!R3RMm`H%_ynWt13gPnpLY=6Yo=%VO1Sbt0N}fuPUReI;K{~
zOng994yo$6S{*m>K~<Sh)k(EFY2q2_aZ0UDnfQ>Z^r-5zTAeoWti%tiRg9Y>99ESh
zsyd@qXH0xVRi07RqiXf2iI1wvF;#t5tv+kwW2*9;svcLX$4&f@s<^6pLam-Kalo%n
zt$sp)G_ESotLh7C^#$ZjsMQx$c~bdD)f)PpQXeYyNU1Nbeo~dEmH$aeJ={v2Rplef
zpOw^^R%(t5w48f}>vkVOCZBh!^>VY~&(FKX%6dHvqK#_Cb=`TrTdOy8py%B;i=_w|
z9~7gYUUNggq<3|@u+Fp_DK?{xdWfdp(WPSOUiK^1qI<qx;_n-GtHmf^s#jmjWP<PF
zZz+e3qoRHU<=|eRgLAnaCiEhx)Qe>uF<SMDwTOF7H>>hnE7nZK3=)ILude%1<cICX
zwpc}<YbS)oS~;jiCV_3%>S5Ky*jF(Oi@PQ|faqFSudW3Z-=qv7Wx=m$f5j&dx)K+Q
zQ5fu)L=NYju<VC^dD*v}4I=Gg3{_KX3`xsDHK-H;>fK}m1jNb}@6tQVS4>gPI;h<<
z5srJI7!@^N&P3IQ0AqP&p@g{nvHm4IdKhr^aUiiv5aRe9<8Jy8$FhgZ{~;24dEEHY
zrviY#M{@g>Kfr(GUiBf)?I0^M+9%PBD)&oMNF0)MG&`U^#BR+>`k*v2Ebke4AF<`j
zL(;;iDre)m!|FqzgE47mL<MP8M(&t;rF=-0$MG}4t^SJb2z$L(ts(~<O-sw;QW3iy
zlbVqCBl13}$}_6`jHDcuE~b>XoO_mFSpitMv)ya84IjYQE|=Q9ThzW=suZ<$TT7L9
zO~J^;er+@(U%P;`n_CWQ>rJejyHcz)eRsiUc3lTN7;`QTN72P5$ChwUE}qQ9Z4@iI
z?l!{uy`YSSaDM95s$VP1al%2Wm67R}gA!ZOZd0QZH`?%}WKBn5vlRK|cEhu_<@rW2
zELL6a)mpLY&%4))(S{q<F^JWmA?c?u{#sNFYHYHI;Z;JvSl*3iRW`G_dCFZ5)~m%+
zGM1G&cdZ_F&V*2#k9He=qBjf-t;!rrDZrNx_vU57x``mdKSw<TmP)D36mb<1=Iu0-
z3Oy3`O4!GJ%0%oh^D>~KK~`X5Mm=;?Sm68S5Z@m;%Bcy-VK%meDTj@Rla=EzmU3x^
zOG~*Cj)R9>HyeKFO)$zsUMK)2QK29xW~pBDBi1Y2G(XGcXGL&RL2m?Wk&dQVz^rb8
zYuH*DZ<MW$C$e?xd`H9zL}mqGS-%nuvlLgX5Z3P(0^qoIN~vC9ip)U9rTxm9j5c24
z9BL~G^a|e2Y}Bj%nc!|%+z6^?uJ4vMiedG7Sl<My(q}FeOB;Tr9x~xfV;6|IcG}?X
zGxn#2@6W7R4DaU~yB;Tc4v*%PRBEF4NNO-O*pp55Ib-QTr$nqxXc6DYV*f+D?5+~W
zY+_kbcGb<2W3dhc)tHT7k~VSJd(@2N^mgU|KiQnMG}n300fXSG9xUNjMuiW_DsUh4
zsm*@w59E_mz@}Kv^<flCnuE(|F8|`}ye9N7x&XDCyE!)>Fw>?1*luoRZa&A|Akf~d
zgGR6-Hg?2~l3ZoG@UoS{V0|N!Diwb%!bVl<29Cy+Fsz4Cpn#)YwA(E78)2a!YwEcu
z2|%ESgIUNqbhXItcnXx0c4nMsoLOhsnQ-L9W|2y$$s+y^roKDFL%WGX3)0~zoPtM=
zvzL-J0OZ?wDJ*ItpKAcnJ;H*d)y`|`Ar3s2QkHPx&S|xW)83;tdszZ&hc)Kf;_!nE
z^{SbNKnZy3Q#0qcZ>n3nlVl2z2ztX^ve$<J3=5YyzzBQWJ;rm)en6#wDg-MSY;mi0
z6k~NF;0j~|Jq#)wGT<OK)vwkuNTF$agKFoxdWhT+^$@!Wv+h$<TS|qOoM@Cuh$DN_
z{9vGmCr_{qAfy4cIi?<FSjj;V<b4LsZT}4he~~@dfk^>Qc!(tz?mVaVFsdP7Glw%A
zvLHd;dnvVDaFBbe_8MpU<Tj4PZTza!YJjzWm9=Fd4sm?IN61FYAft~A%FR3uxhzZI
zA0ao1zPlio{eB_$F!cjM?sTdXa#%26lYJof3#ok|_Y0~0AlKbS3v%B|bwKW0to@OY
zn>5+24L6bi+Y}*8_)U|A;ldy0Hs9(~;m@Vpdvw%RX{U@mmU76Ztef?kR-kgkc1Oq(
z@daH>$f;B*L4#m*Y#b8QByb>6xBeXjLY7Ij-zFn1WIh5LtU=j9!pO$wVybV5zAKci
z<ZDoWaz)@Fu3Z;uZ`c^&0J<5DhGjU&tdKvlyia#e?Y8Dx<N640f?WySELJM@`(R~W
z`4M4y^B)9_i1^C8a&cktilF*I!RcUOEoR*czyv5w@|3@+iQDs*&|1nMIV&c|5?oVy
zS(DYz8)DB&9dQ)Zq||YKRF8@k8I0u)$YTJwC4WhTE8!e4UQ%v($28vLdk@Op2%F}G
zJfEc08-7hl2brpdd23XHNazvBl1Hi`CCR^ehY|Bg+oWlK2LMMy1kH`b>kHRBsRpjl
z<CG-5*gy|uj~L(MJiKWJGYpO}pg<@Ti=1R7(2@57Q`k`l8EkVlIcdv90q+EhrV(&~
z$Q9HZ!pa&N8GyOstVtBT(=5t?nyn(G?}=okg)#;b-ZS$q?;H%Bq;x=jN^|e1e$vUb
z^K5F+nRce|q@77TnZdMk48KR5KKwW190wg8LHr_~S5wCkenf8S2pFev`R}>Zj8h`8
zxPC3_`}^3JAg|%GWDvk6kw+3TJPkZZZWkaVQ6}L9SxpI{eM8WVP}PMy=hSVa_6S)`
zOMOsW5U?I0rYPj7HKBndWNdW%g1QAFyE#B~<v?r#X$3PwJR@`qWRJw~w`_{xy&>>P
z<(l9;6a<0gxNNemFPPWX|DRHS*z)cm#>x6fHuY^XJ0FRQW=v70jjC`<Fdl@&AvI&i
zl1&zXA|_5B=|~4#Vat!&zJXPR6+j3w4yhC1$cF#av2j7e5{Eo82Fl(~L>wJbo4~?I
z!qSW@aEOmm4mb(&0!W!?Ee}M{DS$NPM3ZWBDo!TEcP<f7WeC!P3OIh@&RGSnA=v<U
ziNho(6n+v7Fj<!De>t}-IPRx*u82Gg8GG~xZ0+^bElXjhRrpJ(_CO4&!9=EI9JbJ5
z6@I<5&|%WPGU;7PB(fv1YU$U2uUe>nxp>dY&)tOKi6qDfvEMrZ(&$1&=8vF4PwK2S
z&W|BUR@}C@4cao7=sP!;6tPJNgW4KI@VoV9)EZIeyz}mr{CeJfFuOH7@1A}6<x}o#
zHK-NtQKg`f^l2nOAyA-}s#pm=@R3FlmF1w>DwIGg{1AzxBP?6jQAc5>?{!V9HEa&L
zm8Xf|u|8A}=zGvHP)MZ;;YFR%#N^P|)z*S_!OHgy#WjR9AR!He->}5iQdap{7K+?K
zK|xWC{FNlL`;yfbu{ST^L%f&JnISVAR52`(OnIm9-bv$y;9Ii3gfX}(C*+MvH=!xe
zTpFq!m9aoVXzxQ6<a<BD5+vf(C~EH$c=Mi*(@K@P_Ptk`Mt~V|)`SX<_&chT$nQr>
zL2>nc=u^FyS@SFd%GIJ=z?!jg=vV9aeD68t9Y>HORS-c@rt7_ds0?eZfEAP~W+f$2
zwhROvh<O*kP*}Ng`NlimviCaca2eJbBAkLhfC3Xd-+@q5;oNv0jmG1bF%JDE0yWs@
z98OJua-MWDP$f(uHfh7j^kC`;DC<$AyP&H_($6@D($kPur$9IPOZs`tf%1WrlMk2b
zG9LXF0^&qbhG#YKa7u+2Nk$(@Mj<8HLQ+{y6i*OE4S59)C+1=X_2Puu668UpIuDg7
z7eoUDWpk_qYV$&(0rHNanygR>Od7V7aB19rqKz_(`Yxj08K&WR`l_7Q(8;&($SF~f
z`?Jm`+A!n>6c~ceTNq|F<XQl*Ou7}mVYv~4M`V(2=%q=N3rcO5pIFigpHRBTeONGi
zdGsoP^^W4r!=q#`CzNY#7|Qud0Fspt5Rl}C{|b+2kCZb87Vu>11xKWf5^)llYW@m5
z{|e*}+#L2Gz+<$Z04gaskWlz85eRl5#arR=kVh2=c4n_y>s7+?Ax$R>n(Eu{#$fEX
zj2oB}sLXGJ?}Kh+kje=NvuG;(Ln6HJ7ZZJnI!1OoDucySQmsEx4a^9X8$$}pH?AwB
zVyqcjS#+q4=}}wQ8ULL;5V$r}N8j5f3+g#!^*GD|^Owv57m2Lnk_EXrnbj{@^O6Pm
zIL?Z|lYy^F3m|?R8a@mH?Lw^~x{BLoa3T>GaNIFa!;C@_#{_V)Gb%ES3-Tb1gJa~&
zP%;D3`L~?_8Yu9tS@jB_4gT>J&T<4l&|Qt<2TcvCSN6c6qWTe02VveR;m5%kF6XEq
z)vu)!yw2sMxd2kOm55;tIOWbGvN8YuqI<!;>E0HNySwt<jrTJxa%@?Qt$DZBtlsrQ
zx4s4@1GWNUZ{02?ns}6gWY|tL!g-sMn0N7>Pqb8n0{8<ztZO$1{XSzpNZ77x7>_yV
z0d_@IicFADx7yT^y8%Sr?n3%a_OQ<YIye_hBxYhTSQt_)K=SVOy4Jznif?;&iy;`e
zwV=Fqieu0;TvQN31(5IRzpI0U86u&hNH9wCu3?yBt4eYlZmzfmlMT2kS2ypXDag?P
zb+?+LqqR%*dIc5QqcVW+BpBq_EcfR6_x#X8b}iln@dkLG%+_7sExJwEBU~d2=iM6{
zO-vquaN9)2%^Ay33HGL%kA0z|Vy)!Q!Aul2Ll}<i{(ATH+5F2c?7H~5^L~4nvUuI2
ze8OZ%zsc3iyRnL=gTs!^1s|hH>gv1QXxEzQTST04a$-hw>%xlwKzpl6v}5H>fi^#E
zN_4QoNXh_-D)sKIi{8?;rK@)mExLhrwGSS>ZkcN?ve_fW$kni{xNFVY78b6{URS01
z-=#vO7@o+86+O{@dqC`foOqs`b>6OHE}zfOVH9k_teXpa_N>mk*VfSKDT)j=w_KMx
zG%mvS$U1;UyX6aS!lLNUWrPQmLn9n1&yBqB=m@uq)1!YJ^S>VYr`re7FpzHUMd<o4
z##)_L&Jfd#+DDB?=?Q=PoZNYLR)#SPX@sNTF9$K}0+-BU6tiB7@e#6b^g!fw+e;jg
zgmx^ka07{XFNuEu%z8^X(OgZUf-&il@>*zl<n_HDW$<|hZzB*jQ*6Qkx>n%gj0VhS
z5mA28h}VKq!9aDSBTeAjnIw?rxZ5=`TVa94LTqe8>=ApQi<$AroQXgnx;78~Gb+cn
z45xUSZzTrQN_jF+0#USd@lo<FGcV8NK?E3~u~te9gR?P82%@nxagizxh5=ne&)>nL
z&m&NWrorH6ox|XsrqiS8aZ#mBfbpM7_w{C-(G(u!%ackwW2qCV>C_DB%(a<Tvgn+(
zK8XNyJA6jSGbpt1Qed)>II~QWok&|2CyFIwha<b$f)^#pa)PnE!c0mRLMjggO5j$&
z#89%}0w&mBzkCXuq$Z!P0ISKT0|oArE+~k+zv+R8Js3fy4FL|O0|IBeAi$*|KzOKR
zAMj+D)z;JUB9dP$)nN~SyOvEsttmJ@@Uxf68-u;WyGbOA37%OPZl=RPDTR@ay?_*K
zE3<|4Ut>Hi!Un`jeTo)MkML1s*<wbo@E|y~x%d7l>(bFNt(Y^6%f5HJ5rWf8@P>u0
z{kg+nUKoR*<}S8*N_6GkE|u%iUN^-@@uns#8M;($xIwu!ddqHZf<pqo*WI8r=VJWr
z^1?!(xHN<YrQydLT)ehaSh@K2%`27#HGpCJZqBFb3$Gz~huXS+_iR4@={xR)*W9z+
zOjn|nHF!`w#_5X6!}e0#$s0yeX7LfdeEP9os5mjLboP>vOo_yV7D4eA?wYeA^tD>t
zF~8u0xi6&!Q_)4#yTaf;0>L(|37J^jF)XG+_qdV#vQYyb4B8>p1i@v;uke(Sa;o7$
zC}5_5ZHS%6oq$BT#L2OA>(KoXBKsdYm?&_{)*;Nn_<*x^Rm#E6fHS3_X1~f41-Ta|
z%F1>)ct0gYqmH69UPv+P>H)*Q=@YUc&U0UUL_a5Wf4j3T1i^jliZfBGE)F(L10q{5
z3k}de0s7L~@LtUNY1#5P=5(%-T~85;FD9)f`Ey7i2l=->=%OAD*4&f9Nit|fIq!56
zJ6W$)b_?K2YxVmzEty6K4ACK$0;q!C0Dp-iZv?v}MzUCjh{<A$7+Kwh@e-^{Ve`SF
zL0cDYi*yJU85p1i5sNY`4gw5f%+W3(NaEwMPAw_q5@Jc8IKbJ=r>j|1+DGK<fXY1F
z@G9imDHK7f5?IBc0OElt{(6icsOlB|7uX0Mc6WCLk8@eO(L`4lPY{~|O$v@NYRFCW
z&EpE1<>P)S5?XVwcg-oT)ZL~IamE&nHz697)i^;hVYl9N=~9LfLv9s|hp6qDUV<<Y
zabpS;T3h9U*(S|e#fO_C<q@N!v^+7480evd_4mt5#;{8s$s^)3d@|0n@-`S$8E6C%
z4ZviQ3Pg*6ue~O-?lIV5J~`5;-YAGhJxBCmI9p;xVK8dK@^^q%^rr?QBFeDt0kR>&
zM8|vaZQ{gZ7_1s4PNdm)8oVigPdHQHN>2bk8k-1A4VWEm$YSFVqJW(sMiPl&;YCe2
zfFk5xF5}Vv1_3$PDJlye334kKb(e6iEye|C6dVb;kX~`2y!sX}Sh(GR@t}(%J{GMa
zUtro44>_P?W3z-O6;$2lq!dh+f{mY83B8G-Q!2q=j2_YEAk~JZ7wR(Mu(d49s^7;%
zTD#hKoWMFpXJrW3)FZSJ$g=O7v!UHGR18ph&>4m2lI6wzlwZJGo;y4@ce2&2rmczI
z4@O4!-I{;jjhYSkR0xRz-&iT$^($J|Lej`hn>0-kuh~EsaczwQic@0rb4xi&NFLRB
zLgx!o)aN*47ji73fR<*7CQY-(O^`Uh;@@QJzB#Yn9X2P&MT`JEJ+iY+yW(?b&cX>6
zq&+}(iS~_2$$05VjYd$~f_3RDEJFHRTZr|6Z_8{ChEt%VO&}xybAzDY;t`?rqWb$F
zz8!OVaX3a(pG=RYo=^9tN)*=ON47lWb`XEFpJbaFI_*HnwRQnbO906MwuW_twu=P-
zxBRPttHpN4B1Vmg)i%!5IBU%+^kL>wfom{b*@%9vmC)PGKyM`cGKtMhqPgyHFn}dI
z%%&)qNyf${&h;k=-5!)s>0aZpNhC^MOki0DWC=?k4|C#iD6hVS10SadUxSo%D@9BI
zNa<;%h$|*iaK>#rBJ3cguayE;OIq%4rHH#E$_%Kh7TMtKEojRn3R{~*GbSoNCRg7A
zJ+SBlxCw3$@Z)IJWyE-=z}s}^b3BC|dFX%P&Ivlbm}FqY8R+dNfIW#E({yb)zx^FJ
zw7_ch8SxZBuTb0z2;db1?fzBZP#IV4IcWu>Ocoev7hoUoOfl99h<}dKyAF9pW2ERM
zBMJk-RVHzw{6$j&N^rd}4G1h_upL54I4ny^4DUM*+((jdVs{t}3~y2cQP?cuP?9WD
z`xIR*B*lW0weXs(1?$CBM;Z8UNR_yERKX>R!*mYC<siDSz;S~O$+Zc-z=qPwz%`0w
zZ8@Q23zoD{cHRbnu)Ew5yV((2(L7bc5Ym!?EiJCdae_g<LSj3UaI_Qt^_qvIt3X>V
zt|>mXjU;ABkEh+-r`(E=3b~7<iQ!tUu#uIW<@ZjXz3_O(O`Ab#(W;?YMdX3xj6+{v
zkkPlwu^3XD73l6GFkLdLo3H=?<--b4CUp}S-E@m<@bZY0IH8q&imh}On?Nz!mS7dM
zcFJA6(4~FVhd6Y2l(@-4IpT34cZ9WZ3l!=lx>lk?kJ!@~Ymc!kF_GRVHq7OTT0<B$
zNrkH^)LCidLnYz;46BhE8kUfLsIWu$LTa(jCD6?p4dzscN_Ek1Csd=P-U$X$h#~PJ
zE=MhkkS(=v!=}6lH?0_>3WYKR*)U}O0r}nkEQavY#NBV<MTys+@%=-X8U+hEnx1o>
zb&jVgV2`H8oh(v8H%0Y{Fx7|nlrx@YIGP$2C8&5l5dRaZg8!*lz>3i2{w*S@g*$JE
zr#(2zykIz-X9&s$Edgm6A7WsdL?TcpE&GD=o$X<D3mX7TB$9!0DZvAEz%5YlJzbY2
zeJa+b5)T-yHU$FX0&|{kc#M3oENqrt1TwxL&hTXMI0<p6X)mj+5$?YjUWa8Hypf^B
zl_e;fC1#4q4vPmvp9eT($~X0({B16&=t!_wln17;bce8Pup?tHa!R5oA!2Cp_`@L5
zB&@hC0fhvN`e*5zgSi6RB1(q}n4m>JNmiYUm0HGJ!yDun!NZeOqO>S6LFHn9P>}GJ
znq}w5%<^BOMIKFrKPT%39SC*+Tr@#U&HaY7a<Qz=_Ie^eyKnw2h-QWy!LNW9Bn)YS
zogWlzz`A0#%8<-L?SrcnAV=6Gaw7r^1iWi*BVBwFYDeQ-5I1OmFrq{*I0@L<ad;nL
zLl3K+KcO4K!kurbJ@gG)jJ+#P_xekYDTG9%oTtCS2p9>8&42+pj}sBcGU~il85tk|
z%2~Nlf^F>X!CAjmYi17v2e9ByfESQ?j2iw1Ha`OoQc)<e`olxkkJd?MgMbbxI0%6$
z0ffPA#2yH^f0Bg@X;y824!)biU~)&WJ@hxRms+CD=TFnf1p&MQlRIuE)}SK2CQ`h1
zw@ltkFS$8fI%>?HIpf#z_k*pV0li%@Uk}&MFm}ea@#48l_%}yIRcWIRBr6&X5wnUF
zF@$p`3$>!6$o7aUys@$GvK_+*aM>)n^aLl#y0!qP0Uxexph)1esZ7Q})^qK8v0|u<
z`@(T8mZaVTMWJ9ouN;+Wzt!J<E)T6y#e=eiXyF6Z0#2BBH_&#K9t(c34vVQ(by4w=
zllu!$izIY7MjHbk1L|i`JE5Q{Lyy*;s0a~u%=$4^O}l|^WsMj#1#{`=vA&mE#%#NO
zr(9aJ<zgiP0mF3gk5NvL8cd);B$hmp_;_2nUKQ5F`<E!={WBI!BiQnHje3D(Af#J?
z_H!iBB9`I;HLfJmCBVq6bmAvhV=SaZNakYneg;xcu(nY9HYOMB{wkYi<-(Q>lds;V
zn8-nF6m<ckGWJ#Dm>`{N+=TOp6@`e4pN!`+`Wp;r+O$G@yr5#$jKM2;RPSGZ=i1VZ
zWdVTq&sp<duqHb;E*)AJiWol|k4ht%8?HdGx7l#oR5KCpt89$G6&@t2i%%_2#(_{3
z?`!D!e~_BY<3(jhQ=b4ec@~eMD{krpn1FHEMW0I@MH;nFqj*Sdp24Mqaa=xl0l!Oz
zW#cRhVwJ#sR_a0ubiKrtenbhYLZMtQ6$(a?M=gps$AGsU#OQ1oEwM(wz_edsu*N_X
zder54UuN)=4E_;=f5L!lj@hA{PaAUb_B1c3PT1}=hua6g((KW}!NF_U*GBtB2aaCM
z_GWuWr^k<<K9=pz{#bTs{7uoE03$2`S!}>FI|OPhmrz;$xKA(P(FB2yJMlo^2o0IS
zJ8sP5HauiVyr<=sxfn2fNP2j|Kir<jS1bB?(VWLa4}#_kg1gLH<w42q=+|vv6XL@4
zlW&p(`*++>r;1lDs2g<qeHk4gC_zu-7Vm+k<nD9+&~)Rf`kD+7sw^Q8)*2UUz^ibh
zi9L^uR3ms?6uEQsP6GaHOJJwmlZfPC>zosl1U`Wf+=Fsj@Gz+i#&1S$@i;YtG&uhd
zZ>-yqEO)rt*(tex#fQ%wthzawF{z!1c0yL+tc6HfRF>9wW~MFxHFpTuY;3161~j7Y
z__@qI@;n0Yg<uLTBPa|WxihkQIJ6MvsS*R0z;*T>ga(;^gKs><LJ<nfTM5^N6HLb5
zQ%FVl^xca&j@QwlQBtL*(<Kg+bc4S#Pof5LR>A^<@4_LPBzJ}5gis_6h#5OSLB18I
z>-fT*59A^f+)}|viUjirAz&gJ@EO#aG3+Le9X(4QF{drVK$Jl<pJmH9pJ}jO#%?Id
zpJ^q3*2p$!jcuG(*SAkA{JX_oz#J~;h!mD&?E<_Rwf;Ka_-DRTLfmS%jG*1NYq(ef
zWPuv>Mvn0CTn1MdP;B%5Edt}oV?GsN$x{<<Sh(%?`z-PYEJA!@PVIrLsC>o1&Z$s~
zv88^KYfK?rIrQLxBfs-W(4&kz8HlX|j#xLy+O}4H8e12z)JE(SIn{FG*z+RrAQ#@@
zkXZm&tTwg=M2MX_rIm#{>#_}uJF{#6ZUYEHVEB!+0gW4&Yb@Y^3L9#BUd+Q_(}(0P
zHWo7cdAVDKod5wSi)}~IXFg67MxF6n5LdGLYfR6E|B4`AX5~L7qk%NkLV+m=B{c{r
z*prfkD0W)-zp2uo8~_0t+ZkmrXV_-44t507DFfMb>NMqIoV7H6HJuOv{tj_EndTn^
zueW)5nx11)adN)Js<UD1u5x>$7(EF03J;pW9$Z##ufcKl!EJn@r|@9Yy$x&5@_P@q
z_Hx0i&AB~N3SdZg6P~K5u(`MQ@g<WcXP9*S?`05_p>j_egXwwhiI!{(`TtdKnob(E
z)uv96rV_-+uFUd#TL8P;#8kOn{Ck`xNve^F;&rAEka(LKRv}KN@Yl7Oy6x9-(RsI9
zD6&Mh<@fUU*&V2b`>lOkQ1pKQzVrT&!3qN^P>m!-BxrP8B*)$d3?485MN^^ycp^cF
z!RhU>{FfN~I|L%A;3O3+t0tF}c;GCy#<!$|VtC(VVM+n*!rnhX^g!r`$O$W?7I}23
zj?mi^zMY^L^zzK&H-l#~btpCB^nj8~z~eLaG+Hud(XXSP_qz;!kAc9He1<2$KF7B&
zBWO=Y-kzqo@eOqMDb8sCFMUH$)b?k`%+>fpw@~qUsPSthl#@ShgG+cc$U>)N0S#GF
zvVd3ti32>!prTj-fdUc-EbyX8`w-u_0(HbYKE#E0yqIGB@pri-g$3|AE__vMh?0nG
zfnyl9;DN^kicXP4qzioXO3HR-iAg?oVf;v`MJiu{&A-eqVa*ydYW&r>Mc=n>lD2t~
z!FY>y5yp@JHwt_RCep;^dIdr}+>s)P5$>Bo#puM`MS8i+y16g<8_jaj6;K-;kJXGd
zns+NfDG&P!T=PpC`C=sBn>+KRd-V!*BU`&?@F{||O0aWgt6YBhvs+xNe51Vf|Amt-
z;p95D>*K<S;k#(5r+fl--MnDenipdyFz-+rL&0m_u`k<_47a67GIE=8-{T|VjU0R<
z<197>pB3aL02OqJD7BDA9Yrp?RYdecCWaDSB9buQZT#^xQ3ny%FA;UnDyUL3xaZ3M
zvc{T^4DpV*j9)#BfNU{l0jn9^jhU^#f<R6MY4&n_hGI6X+I*klI1I5+29i$W-cDFt
z_pN(vo29d)xY~q!-2O$`KK~52z=ECru;y!x-k)GR39ku`vD!QkV2UgRt6`zq{3=1p
zu2sL7#d`3@r~s1ptSLC+A1p2+cVOr0SxFS@mq`Rp#{QxVLSQH4?Ei`eq%)&b_Wl%c
zkIfq~++}te0dy+HR>iv2gMk5eNkM?X>p)oXuMrd^ObRMOlF|}K%7;J^fE>e%0f;d*
z(GVq{QKOpyZ#{D|nkv1=H(I=(j0?AA26+Y_+ggCeyy%iTtK=X|n2#60N7b^J+C6Ct
z<$}4B2{pYK`K6)Gy(eHcANBM80^>9q%m0kG_Ez-%oKZYaUF{)Wi>);vF#a_cfiTB@
zeEQ%g&imnl|5q?m@4q1U;e(&R_}v8fx$CJk;hvV=Pj!pg{rn0Y^btoT3SC2wNb5KQ
zj_42qNrycgHdx?}UaOtbH#*b%m`>aQ?Eq8@JWKosza=CB(NKfrsPk<M{wmPJ_j0hr
zux3q9@L!wDKeonjs%|?IW8y5;YT4lJkN3NNn&;F~nb@N_VP*Tl-Jq>lK%{Iiu}B0I
z$Bcue=G#36JbB%hFNhS{NV@FtV+aR>B#4t^B=PULLFpEth6aJ8v-pp<E2i*YiJO&d
zLN6ZKuwTS}`JVJ<@Xo+K#g3y&6mMyD#5(lK7L>GJq(NsTnykanu2Iv5?+;<W4ym25
zh>kQxrR%O}+p<(uu_>sn5Tcgxkb?>fW_WyQC@m-D*VT4IcKe8;6<zdNpW$Z*PzPPp
zNRBsl4P!bQ8B>R8O@|TQD9cdikUC=YaSiC-hM<4@Kg`dD-=*xjOjVir2GtNWZ%`nj
z-Z&b8d459eP4bMv%MT9d59OUIHhiKFV1r8T9fxiSNH;1uNv8t?K7&X)PyC?2lujJU
z{Z(%#7p+#mb--Ye$A-T9x0i3oWnKP$WE}f7V%EE|-87f3Kjr2$ZtT)|`dt3p+<XFA
zz^x9S8R$~c-8H(pm-f}wQDr2WI$ZtO+;ekATjrj2S!;7|u3KZ*S)MoC?!z-6@u2`S
z1luR{gVm<{5*jmMkkG>Yok(s3OpJPxm*cR6-#>orxDB_M+w5)u`7{Q7#C^J_tFZff
ztxankrj|NQ92%B#^lvm~4^i_7%S{Hu6TDAO?_>0AT`D$W)csU!-C8x$jJr)Sn%xbA
ztH0X}ygy>?KW6X;2qakto8EtnBVlvXRN1suHo*-*#%)$NVUd%x5X~H{+U+k%WM08A
z)Qcc7sj1#cTo}&u7;}wLzES;#G-pQCZ(~%(K?!Fr`dPi^*HM38_1m8@L)34=fq$FP
zzhv;=7<?H)o36^+)0C_K6T16p&X?|ZeZx@6jSLQs_GJgNgQHWqIV5_&$G~IoqYPeV
zfXA$6NteX+2A^5M2d^o=3HM-Bwj>cS^zbv55T^0bfAM4y9|Bzp8oPPxpOnA1?U8zW
zzsZ0bK(0iYGEg|-^V%Mpk)9;g6H}n5K;*&+wE}X7#Qy&e;Laug{{TvO9!0%~MmIP^
z<FjS?*4Mi8q(P#w^F~<a@P1{0f$<r7^J#Jdsk;&J0+R6x4Dxoo^q%#8k*)Fs*-CWW
zHaBzlCf8)Vc@~^t@C^oBkb_%)S)KF7$>lYISR82#W0mEH1MoG@Nqp&QEH#FA{MY%5
SSQcr0sp;>k@2cVHcmFTQxdQh9

literal 0
HcmV?d00001

diff --git a/recsys/algorithm/matrix.py b/recsys/algorithm/matrix.py
index 9a8fa53..85da209 100644
--- a/recsys/algorithm/matrix.py
+++ b/recsys/algorithm/matrix.py
@@ -73,12 +73,30 @@ def get_col_len(self):
 class SparseMatrix(Matrix):
     def __init__(self):
         super(SparseMatrix, self).__init__()
+        self._values=None
+        self._rows=None
+        self._cols=None
+
+#`nrows` and `ncols` specify the shape the resulting
+#matrix should have, in case it is larger than the largest index.
+    def create(self, data,row_labels=None, col_labels=None):
+        self._values = map(itemgetter(0), data)
+        self._rows = map(itemgetter(1), data)
+        self._cols = map(itemgetter(2), data)
+        self._matrix = divisiSparseMatrix.from_named_lists(self._values, self._rows, self._cols,row_labels, col_labels)
+
+    def update(self, matrix):
+
+        self._values.extend(matrix._values)
+        self._rows.extend(matrix._rows)
+        self._cols.extend(matrix._cols)
+
+        self._matrix = divisiSparseMatrix.from_named_lists(self._values, self._rows, self._cols)
+
+    def squish(self,squishFactor):
+        self._matrix=self._matrix.squish(squishFactor)
+
 
-    def create(self, data):
-        values = map(itemgetter(0), data)
-        rows = map(itemgetter(1), data)
-        cols = map(itemgetter(2), data)
-        self._matrix = divisiSparseMatrix.from_named_lists(values, rows, cols)
 
     def empty(self):
         return not self._matrix or not self._matrix.values()
diff --git a/recsys/algorithm/matrix.pyc b/recsys/algorithm/matrix.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0c5cba451931863960257260cb5c4e1e2c7a9d74
GIT binary patch
literal 7182
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
Ge)iwQG9Op~

literal 0
HcmV?d00001

diff --git a/recsys/datamodel/__init__.pyc b/recsys/datamodel/__init__.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8d3822860cf95760a3f628de56e2c4b7be24950d
GIT binary patch
literal 226
zcmYLDK?(vf4D4D#5j^>cUbG(&5g#B6B8Znz+h8m0R@&}ipX(d^fa$VeNG6kENW#DC
zRp>r9&8Fbgk5qX>U?>}AX6(t?oj7L~F(<uLFsi7LxPoCMea3VaO*JeC0F?Itf++h#
zzS1ad3@F_rDeHo42W?dfZHh0{CCii27JW=rknf?`*liQ)JY+ewdOJjeUIMtz6@VT2
Qmb7_Ka_{j_zsOYV2d&&UHvj+t

literal 0
HcmV?d00001

diff --git a/recsys/datamodel/data.pyc b/recsys/datamodel/data.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..57a97e63b45c10a3996f3fd0a234824a80e45c99
GIT binary patch
literal 8343
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
S$duvH5qV6NW`J5|O8){sGDSuJ

literal 0
HcmV?d00001

diff --git a/recsys/evaluation/__init__.pyc b/recsys/evaluation/__init__.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0c3acb92feecc6b7ce41ead6f886938ec678ce78
GIT binary patch
literal 269
zcmYL@U1~xx5QXP@s}`jTa0mCL%?3o;B7G_<{iFCY#3Yz$^CL+V?s8p2H_%BDIxyeN
znK=)q{kfU=aQ;+qsg~zGcb+jcz?Rv79)NCP-@=ynD|qdO{G}12Tvwk_lqrTXVR1-C
zlPXn?Stc32DePC>pt`4wm-YK<iBHe#hqpfmqI3?i;}`rRf3OrbdLw9;q$Mvly|Oa-
zO^RPel`I~mvc|<!58}`l8^difD)(7jhB--hlr@V$q(BAnj*T_*+(&(MX3_ATYqnAI
E3qL|aQUCw|

literal 0
HcmV?d00001

diff --git a/recsys/evaluation/baseclass.pyc b/recsys/evaluation/baseclass.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fa9dc9bb8e2b08d3939d210755c6ca6b9c77ad9b
GIT binary patch
literal 4781
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
Yw>;|_Pr-Y;;k<AYp~b<r<L&r=0va5U;Q#;t

literal 0
HcmV?d00001

diff --git a/recsys/evaluation/prediction.pyc b/recsys/evaluation/prediction.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4631a7f19c45d8d8446fa8521b00acfb5fd92c82
GIT binary patch
literal 3802
zcmdUx>u=md5WvUwJ(5d818LKwmB3IT9a6}tq7qUSDiRX<g<PY((8oz&ZR}0X!H<h~
zui{YhrHK#{U-%Q@Z{RBu%*^^8E$z29P$xULFRy2IW`8^0p9_sN`{Qv4<>TS+mss@A
zC?foKAOT1n=sMs~+l9o1)Pt^P+a4qpNUPAT+I9t!8l-jT*0Eg$Rfj_dq8eNPoC7@R
z&OlTj_0EiYXCay$^)|-64T$ET)0{_D;=l1}I#@FmQtzZNwl#yTzLJH`v%>bwVUKw5
zZF@WT`kM!v`?j-zojXruGLR<DGec(XZtgs1_7?W(Pf#czYth)Q3z0`#aDnz$xQ<O1
zE?hEKbegQnFZfEs$7ff`%-=lFc``7{-zkc`Xz;+@zAR+wN7Bf<zVywYpC~^>HDsK{
z*^zI0%D-RagDmp*i-GC!%r#$Pn}+t4Hr!<wGsC_r*V;&8ZPt8}_joXLQ5h;^;8$4m
z4HO3d9XLe~xNz*ksRQTMn;qVqc9UkCDor2sRUybMLmL|esGtdgCM{_<UL}WIA!C}@
z3oP_wthIW1s#@_uA$xJ!+8c&FS)_YKeyl>HTU#>hsU$C`qtzdpUY>ngs8A1etNc-8
z|FrsrisF!5wca0!8ZLeXizYR|2}xbjmipPq-?4&L6}RBjC0QDbHoLHbwi=gA@1Wap
z6OJonep#<l{RbEH9+(=r8k^4?$k5z6?LoO|vlP4zy8g_AAKPFsGQH6fyc{mS{oNfn
ztCU|jt-$IT@A+{RxPkkQuVB|$tIu(<lV|YAYHQ|rf>n{#x`w)trdD|lwzj^5rk1f{
zI94c1#u1{G(SA=p#Q=#94^^Qu<Lme*r9V8v_xOoP@|j|fbJ+2DkOx|@pJ{s&d~*SR
zgCm|0@VEGdz+gDy1^_(7<D@vu1$o1UO&x1tp7!ww*!5+0WNU{>E=`kX(j?ppfC4ui
zWkCLBCG;TWf%vjwi{k1%EP5FQIP=bR_kHJzx8&5E51a-3TXEV=QnEY{nM_p>u>C=h
z=FuRbCK(h2Q66HQPER|}6o;*#^(VGm!eOD$&tTD{bgfo%<~>$P>~>xc?|#K~88#x%
zjfGz4WFU*T4|z6hiID6orko&G3fjPl*WNtc=-PRaZlnm)?Jukl-EK48ZkKdJER-lI
zf3B9e@GR>ri5Mj4f8t(k#JcYZZqKo92F&b;b&YA(Jp|+uv2JQPX5Ge!btT>`>uS@i
zdzpgFyx+#mbHU6r_NFu}u}9>yro3MA5!J*j2`nH8WQ;w*c?0Ddf~UMgHKr8@2%TfG
zNa9@-6O0Lhhgc$UmBcj~#c~<<BI&e-Wz4RRUtpJCvP%%$#B~xYBvwffA1)tHw3#1a
znZ^@E`ku|<Lag#bH_;XreG7$pRYr!n7!JK87b};;EUPF~!b#s-l#AK8yNQ<n*I?Qv
z7mzP7?V<~t3f(Xarq8fardp%i@*Jn&u^r>0<PIj%4y+<|C=FxIwrw6lpq;XoPZ=Lh
z`jD8MMpA*3Rp#?$9<clPWlbi7v-JNvP81_wkcN}nzTG5}@tvPa(__pb#Boj-_lN7+
zNTUU9uOX7z+<SeJd)pk4*8SJe`hnCcOr+Mr!*Ndd1k;hGPn^QFH+pGuGPYsUuX|*R
RrchaQ7F{gf@{Q%2{{X&ENcjK&

literal 0
HcmV?d00001


From f7c75e71da012a1a7f50e000d0d1fefc9ad999ad Mon Sep 17 00:00:00 2001
From: Ibrahim Abou Elseoud <Ibrahim.elseoud@gmail.com>
Date: Tue, 25 Apr 2017 18:00:29 +0200
Subject: [PATCH 2/7] Added ability to read batch users or items and fold them
 in. Also added ability to foldin or truncate new items not in the original
 svd model when originally trying to foldin new users and vice versa

---
 recsys/algorithm/baseclass.py  |   5 +
 recsys/algorithm/baseclass.pyc | Bin 11549 -> 11617 bytes
 recsys/algorithm/factorize.py  | 251 +++++++++++++++++++++++++++------
 recsys/algorithm/factorize.pyc | Bin 25820 -> 30045 bytes
 recsys/algorithm/matrix.py     |  53 ++++++-
 recsys/algorithm/matrix.pyc    | Bin 7182 -> 8411 bytes
 recsys/datamodel/data.py       |   2 +-
 recsys/datamodel/data.pyc      | Bin 8343 -> 8353 bytes
 8 files changed, 260 insertions(+), 51 deletions(-)

diff --git a/recsys/algorithm/baseclass.py b/recsys/algorithm/baseclass.py
index 681b805..8310a7b 100644
--- a/recsys/algorithm/baseclass.py
+++ b/recsys/algorithm/baseclass.py
@@ -39,6 +39,11 @@ def __init__(self):
        #new for update
         self._updateData=Data()
         self._singleUpdateMatrix=SparseMatrix()
+        #new for batch
+        self._batchDict={}
+        #new for foldin additional
+        self._singleAdditionalFoldin=SparseMatrix()
+
 
     def __len__(self):
         return len(self.get_data())
diff --git a/recsys/algorithm/baseclass.pyc b/recsys/algorithm/baseclass.pyc
index 16b11748948af3db710aff504e77c4d4593df659..50b4b32a88900c7b87a8c0da46433b026d69f6b3 100644
GIT binary patch
delta 1145
zcmZvcUue@;6vuxjO_Qcc|D+~O+NP~d`|#`1&2Gb#A}A@fZmj<4tziGiex%8d_Gj~_
zBzFt6tBRs-P|)q{p#^=JYz!+C$-oC;um?d93cl#uMxV!?HW0)I@Asw;QY7%nJ?DJS
z{hfQy`Tg$9?aq1Y{%dOd=joe=-WE5_bHshT&oPW|48v+Pq|3NkTl<ZyVN9E37{)0x
zz)S8Q60%Vl)<Rllmd2folo6O^r?8l_Q`xK|onj%Kn<(b8PJv&$uZ3Dw+@OH^`N&tN
z{#`y7->jdc5cf4W0WZ?6oL#W2VV?4AM%$G`U{hJAP?jar1y6{{yGEb#y7#Wmtk5WL
zHLTTk;fuZwO7W)ebyEnLZiQ@Grukv-1z*{g7K{_z=Ko3QDdE&(|7oR<@v*>jig2*0
zuUTi-BmJwDoUKxAkBBRCwz}OECzZI<yiE($)t2+*hgO)t5bw5rO)J%z;2?GB+HpU^
zuOK}NnUbBBwutfVw&dQ!yOn#bnmVve6ytwG(J&kWVlfPS41B_!;h0Jo316if{5bq)
zYcJd}AOIZYjrO<_JZQg8KUR~G2@3C5KsLtTM<X6xZ;F45uF~!5V8<j4yiYrz5=`8>
z*wb%yuG6m^>H4;I;1og=e6Q;k{mw^Xi{a1qB_KYoE+pSrvNOee-Y#Sx@kiYiRpu|<
zSLiVh_Wa-Z&KaTa0D6F9JkopEk2^Fm6{_Wx-uv`Nb>QHri(c@T@yj&9f5*eB)4TN<
zp6PF<m%P|No}7l-0x&A3Zh$_)f-)tBcUg>MiET4>LE5FVIM2KNNq<^-1Y{z`L9gXU
z7p!Hc;K*lpHwN6J^9Y?&aBA;B5cd97!KZstI|fX_Pt16$E=&&v3KMyLW%f_$%4T3M
zD(E3`%2ugZ5wmdOVbZ6tQgjNkY&lt>@4bZJ9D+FC$<&N>=Je;M&Q6OZULO3J{^H;V
zl}llWqCgj*>(QO=gNdovBQ26xH3J_4hX6f=wb6n(1{?=Y0AB!;z!aeGC6AT*WEgl8
f0RC=hft|zwYT@<7kmnNO!~7u8qh64m#A$j9+~?>)

delta 1159
zcmZvbOGs2v7{}-2I65z9nz~Monl@UDk2IkKdtlip*hBNAW|m#2@tU`39^HFYN~|b?
zc10gi3)?8I40;R%5d^kq8Ci=65(-+is70;X)cK#O5Q$-a-0yYHcmCfwp9b#^7UX^R
zy4;&SKPvFqSqBB}?Vb^v?Qn$IY__h5i>B>2X?y-vzem_M<`F+9bLM#%z0E(s0#xa+
zSRLJR+}W^J#JM71<q4RU5m^OK03Ix6Qe*hEQ&kNuW2kBaEfmbjdYxVs7O}0gR5<m=
ze&H-(JL!Y-r?B6-F0eN0chAb$m_zQ+C4yyi#k0s1>ho?8p-HcqUnw@}&3^NSnQ#Pr
z^K2;l+;^P0(E;BC)QWa>dg!ye%-Dpcj>N23B0ICXhLy_ZUC40@SPp@>p~WnXhv;LV
z`S0eX!atF{x@MlS5H%_56gW7tc>~Z0Y@riMnW&gjuCUAWOIh%j!yN*kBHu}`i^Hye
z!stSnQ92n6vg_IN!49@#Wjbnh3s_Ddol$j5OPUeLEal~`@I&Jimhu#cFH=wIXJW?f
zY_jA4tN!1LVUe|b4O$9KvpG6edM&3I7Ufa;T6%*$po?Xvl#Uezh`0Sy)X?JTWKv6w
zJSL-jQXKzF`DONmlIwqjmPvz}AW#PEqN$2)ZfGAt&pbfSD(<tV*|Ex&Rcw)F!<T|l
z1aVS%zNq8?Z>6SioMx(u*c+O!iZ=J4dI-Rsi^xIBu@4JV)+BfvKCw+)OIey>@<G~I
z-RxF{hYtg-)Z<g#7^a%gQ@ZtX?sc`j<tRdZ0($O6tcJaE;_%7Q=8gsP5IJkYe%YA3
zT=Fp_Xh%&|kL;`;Nt%GXY`Uo$=}CSBPHZMeok{B{%T)CdIbfbaZ~#G^@8F&R^-y<b
zSMOmyN)Kx8vG>$hJ9%*pl8S*6K=vc2UjY-(lUD*;fg+#|*bK<aoV%egcLRHYcAyj3
z2Xq5cmju4f3q-^pp#62@%tZ@z^&6)V!{{x)fEm{_=NjT?#!aDp!^BxGd^VoS2I{-n
EZ#Zn_vH$=8

diff --git a/recsys/algorithm/factorize.py b/recsys/algorithm/factorize.py
index 6d41edb..c5ddb53 100644
--- a/recsys/algorithm/factorize.py
+++ b/recsys/algorithm/factorize.py
@@ -73,7 +73,6 @@ def __init__(self, filename=None):
 
         #Update feature
 
-
     def __repr__(self):
         try:
             s = '\n'.join(('M\':' + str(self._reconstruct_matrix()), \
@@ -360,9 +359,9 @@ def recommend(self, i, n=10, only_unknowns=False, is_row=True):
             item = self._get_col_reconstructed(i, zeros)
         return item.top_items(n)
 
-    def load_updateDataTuple(self, filename, force=True, sep='\t', format={'value':0, 'row':1, 'col':2}, pickle=False,is_row=True):
+    def load_updateDataTuple_foldin(self, filename, force=True, sep='\t', format={'value':0, 'row':1, 'col':2}, pickle=False,is_row=True,truncate=False):
         """
-        Loads a dataset file that contains a tuple
+        Loads a dataset file that contains a SINGLE tuple (a dataset for a single user OR item , has to be either same row or same column depending on is_row aka tuple)
 
         See params definition in *datamodel.Data.load()*
         """
@@ -371,82 +370,239 @@ def load_updateDataTuple(self, filename, force=True, sep='\t', format={'value':0
             self._updateData = Data()
 
         self._updateData.load(filename, force, sep, format, pickle)
-        print "reading the new tuple"
+
+        if VERBOSE:
+            print "reading the new tuple"
         if(is_row):
-            nDimensionLabels=self._V.all_labels()
-            # print nDimensionLabels
-            self._singleUpdateMatrix.create(self._updateData.get(),col_labels=nDimensionLabels[0])
+            nDimensionLabels=self._V.all_labels()[0] #get labels from V matrix to complete the sparse matrix
+            print type(nDimensionLabels)
+            print type(nDimensionLabels[0])
+            print len(nDimensionLabels)
+            self._singleUpdateMatrix.create(self._updateData.get(), col_labels=nDimensionLabels, foldin=True,truncate=truncate)
 
         else:
-            nDimensionLabels = self._U.all_labels()
-            # print nDimensionLabels
-            self._singleUpdateMatrix.create(self._updateData.get(), row_labels=nDimensionLabels[0])
+            nDimensionLabels = self._U.all_labels() #get labels from U matrix to complete the sparse matrix
+            print nDimensionLabels
+            self._singleUpdateMatrix.create(self._updateData.get(), row_labels=nDimensionLabels, foldin=True,truncate=truncate)
+
+        if not truncate:
+            additionalElements=self._singleUpdateMatrix.get_additional_elements()
+            #If it's trying to foldin a new user who has rated a new item which was not used before, then foldin the item first then foldin that user
+            print "dimension",len(nDimensionLabels)
+            print "additional elements:",additionalElements
+            print "length",len(additionalElements)
+            if len(additionalElements) !=0:
+                for item in additionalElements:
+                    if (is_row): #if I am folding in a row then , the additionals added that shouldn't be are the columns to be folded in to the rows
+                        self._singleAdditionalFoldin.create([(0,nDimensionLabels[0],item)], row_labels=self._U.all_labels()[0])
+                    else:
+                        self._singleAdditionalFoldin.create([(0,item,nDimensionLabels[0])], col_labels=self._V.all_labels()[0])
+                    self._update(update_matrix=self._singleAdditionalFoldin,is_row=not is_row)
 
         # #update the data matrix
-        print "updating the sparse matrix"
+        if VERBOSE:
+            print "updating the sparse matrix"
         # print "matrix before update:",self._matrix.get().shape
-        self._matrix.update(self._singleUpdateMatrix) # updating the data matrix for the zeroes , also for saving the data matrix if needed
+        if self._matrix.get(): #if matrix not there due to load ignore it
+            self._matrix.update(self._singleUpdateMatrix) # updating the data matrix for the zeroes , also for saving the data matrix if needed
         # print "matrix after update:",self._matrix.get().shape
+        self._update(is_row=is_row)
+
+    def _construct_batch_dictionary(self,data,is_row=True):
+        '''
+        
+        :param data: Data()
+        :param is_row: Boolean
+        :return: constructs a dictionary with the row or col as the keys (depending on which is being added) with values as the tuples
+        in self._batchDict
+        '''
+        # self._values = map(itemgetter(0), data)
+        # self._rows = map(itemgetter(1), data)
+        # self._cols = map(itemgetter(2), data)
+        key_idx=1 #key index default is the row
+        if not is_row:
+            key_idx=2
+
+        #collecting the significant col or row tuples at one place to fold them in at once
+
+        for item in data: #data is a list of tuples so item is 1 tuple
+            try:
+                self._batchDict[item[key_idx]].append(item)
+            except KeyError:
+                self._batchDict[item[key_idx]] = []
+                self._batchDict[item[key_idx]].append(item)
 
-    def update_sparse_matrix_data(self,squishFactor=10):
+        #batch loaded , now need to fold them in one by one
+        print "Batch loaded successfully"
+
+
+    def load_updateDataBatch_foldin(self, filename, force=True, sep='\t', format={'value': 0, 'row': 1, 'col': 2},
+                                 pickle=False, is_row=True,truncate=False):
+            """
+            Dont forget future work in presentation , remove old and insert new
+            Loads a dataset file that contains Multiple tuples
+
+            truncate:boolean-> sometimes new users rate new items not in the original SVD matrix so would you like new items to be truncated or folded in ? default is foldin
+            is_row: boolean -> are you trying to foldin a row or a column ? yes->foldin row , no->foldin column
+            See params definition in *datamodel.Data.load()*
+            
+            """
+            # call update here until it finishes
+            # nDimension
+            if force:
+                self._updateData = Data()
+
+            self._updateData.load(filename, force, sep, format, pickle) #load array of tuples
+            print "Reading the new batch"
+
+            self._construct_batch_dictionary(self._updateData.get(),is_row)
+
+            print "Folding in batch entries"
+            nDimensionLabels=None
+            if (is_row):
+                nDimensionLabels = self._V.all_labels()[0]  # get labels from V matrix to complete the sparse matrix
+                # print nDimensionLabels
+            else:
+                nDimensionLabels = self._U.all_labels()[0]  # get labels from U matrix to complete the sparse matrix
+                # print nDimensionLabels
+            length_of_dict=len(self._batchDict)
+            i=0
+            isbatch=True
+            for key_idx in self._batchDict: #data in batchDict in form {key:[(tuple)]}
+                print "user:",key_idx
+                i += 1
+                if (is_row):
+                    self._singleUpdateMatrix.create(self._batchDict[key_idx], col_labels=nDimensionLabels,foldin=True,truncate=truncate)
+
+                else:
+                    self._singleUpdateMatrix.create(self._batchDict[key_idx], row_labels=nDimensionLabels,foldin=True,truncate=truncate)
+
+                # if(i==length_of_dict):
+                #     isbatch=False
+
+
+                # If it's trying to foldin a new user who has rated a new item which was not used before, then foldin the item first then foldin that user
+                if not truncate:
+                    additionalElements = self._singleUpdateMatrix.get_additional_elements()
+                    print "dimension", len(nDimensionLabels)
+                    print "additional elements:", additionalElements
+                    print "length", len(additionalElements)
+                    if len(additionalElements) != 0:
+                        for item in additionalElements:
+                            if (is_row):  # if I am folding in a row then , the additionals added that shouldn't be are the columns to be folded in to the rows
+                                self._singleAdditionalFoldin.create([(0, nDimensionLabels[0], item)],
+                                                                    row_labels=self._U.all_labels()[0])
+                            else:
+                                self._singleAdditionalFoldin.create([(0, item, nDimensionLabels[0])],
+                                                                    col_labels=self._V.all_labels()[0])
+                            self._update(update_matrix=self._singleAdditionalFoldin, is_row=not is_row)
+
+
+                # #update the data matrix
+                print "updating the sparse matrix"
+                # print "matrix before update:",self._matrix.get().shape
+                if self._matrix.get(): #if matrix not there due to load ignore it
+                    self._matrix.update(
+                        self._singleUpdateMatrix,is_batch=isbatch)  # updating the data matrix for the zeroes , also for saving the data matrix if needed
+                # print "matrix after update:",self._matrix.get().shape
+                self._update(is_row=is_row,is_batch=isbatch) #Do foldin on the singleUpdateMatrix tuple
+
+            self.update_sparse_matrix_data(is_batch=True)
+
+
+    def update_sparse_matrix_data(self,squishFactor=10,is_batch=False):
         #update the data matrix
         # print "matrix before update:",self._matrix.get().shape
-        print "commiting the sparse data matrix by removing empty rows and columns divisi created"
-        self._matrix.squish(squishFactor) # updating the data matrix for the zeroes ,#NOTE: Intensive so do at end
-        # print "matrix after update:",self._matrix.get().shape
+        if is_batch:
+            if self._matrix.get():
+                if VERBOSE:
+                    print "updating sparse index"
+                self._matrix.index_sparseMatrix()
+            if VERBOSE:
+                print "before updating, M=", self._matrix_reconstructed.shape
+            # Sim. matrix = U \Sigma^2 U^T
+            self._reconstruct_similarity(post_normalize=False, force=True)
+            # M' = U S V^t
+            self._reconstruct_matrix(shifts=self._shifts, force=True)
+            if VERBOSE:
+                print "done updating, M=", self._matrix_reconstructed.shape
+
+        if self._matrix.get(): #if loaded model there is no matrix
+            if VERBOSE:
+                print "commiting the sparse data matrix by removing empty rows and columns divisi created"
+            self._matrix.squish(squishFactor) # updating the data matrix for the zeroes ,#NOTE: Intensive so do at end
+            # print "matrix after update:",self._matrix.get().shape
 
-    def update(self,is_row=True): #update(tuple:denseVector tuple,isRow=True,,
-      print "type of S",type(self._S)
-      print "type of U",type(self._U)
-      print "type of V",type(self._V)
-      print "type of data",type(self._data)
-      print "type of matrix",type(self._matrix)
-      print "type of matrix reconstructed",type(self._matrix_reconstructed)
-      print "type of matrix similarity",type(self._matrix_similarity)
 
-      print "dimensions of S",self._S.shape
-      print "dimensions of U",self._U.shape
-      print "dimensions of V",self._V.shape
+    def _update(self,update_matrix=None,is_row=True,is_batch=False): #update(tuple:denseVector tuple,isRow=True,,
+      if VERBOSE:
+          print "type of S",type(self._S)
+          print "type of U",type(self._U)
+          print "type of V",type(self._V)
+          print "type of data",type(self._data)
+          print "type of matrix",type(self._matrix)
+          print "type of matrix reconstructed",type(self._matrix_reconstructed)
+          print "type of matrix similarity",type(self._matrix_similarity)
 
+          print "dimensions of S",self._S.shape
+          print "dimensions of U",self._U.shape
+          print "dimensions of V",self._V.shape
 
       invS=np.zeros((self._S.shape[0], self._S.shape[0]))
       for i in range(self._S.shape[0]):
+          # invS[i, i] = self._S[i]  # creating diagonal matrix
           invS[i, i] = self._S[i]**-1  # creating diagonal matrix and inverting using special property of diagonal matrix
+      # invS=inv(invS) inverting with numpy
 
       #if new is row -> V*S^-1
       if is_row:
         prodM=self._V.dot(invS)
-        print "dimension of VxS^-1=", prodM.shape
+        if VERBOSE:
+            print "dimension of VxS^-1=", prodM.shape
       else:       #if new is col -> U*S^-1
         prodM = self._U.dot(invS)
-        print "dimension of UxS^-1=", prodM.shape
+        if VERBOSE:
+            print "dimension of UxS^-1=", prodM.shape
+
+      if update_matrix:
+          updateTupleMatrix=update_matrix.get()
+      else:
+          updateTupleMatrix = self._singleUpdateMatrix.get()
 
-      updateTupleMatrix=self._singleUpdateMatrix.get()
       if not is_row:
           updateTupleMatrix=updateTupleMatrix.transpose() #transpose
-      print "dimensions of user",updateTupleMatrix.shape
+      if VERBOSE:
+          print "dimensions of user",updateTupleMatrix.shape
       res=updateTupleMatrix.dot(prodM)
-      print "type of res=", type(res)
-      print "dimension of resultant is", res.shape
+      if VERBOSE:
+          print "type of res=", type(res)
+          print "dimension of resultant is", res.shape
 
       if is_row:
       #use new value can now be concatinated with U
-        print "U before adding", self._U.shape
+        if VERBOSE:
+            print "U before adding", self._U.shape
         self._U=self._U.concatenate(res)
-        print "U after adding", self._U.shape
+        if VERBOSE:
+            print "U after adding", self._U.shape
 
       else:
-        print "V before adding", self._V.shape
+        if VERBOSE:
+            print "V before adding", self._V.shape
         self._V = self._V.concatenate(res)
-        print "V after adding", self._V.shape
-
-      print "before updating, M=",self._matrix_reconstructed.shape
-      # Sim. matrix = U \Sigma^2 U^T
-      self._reconstruct_similarity(post_normalize=False, force=True)
-      # M' = U S V^t
-      self._reconstruct_matrix(shifts=self._shifts, force=True)
+        if VERBOSE:
+            print "V after adding", self._V.shape
 
-      print "done updating, M=",self._matrix_reconstructed.shape
+     #TODO: contemplating removing this segment and just reconstruct in the updating spare matrix function
+      if not is_batch: #will reconstruct all at end with batch using another function
+        if VERBOSE:
+            print "before updating, M=",self._matrix_reconstructed.shape
+        # Sim. matrix = U \Sigma^2 U^T
+        self._reconstruct_similarity(post_normalize=False, force=True)
+        # M' = U S V^t
+        self._reconstruct_matrix(shifts=self._shifts, force=True)
+        if VERBOSE:
+            print "done updating, M=",self._matrix_reconstructed.shape
 
 
 
@@ -475,6 +631,13 @@ def update(self,is_row=True): #update(tuple:denseVector tuple,isRow=True,,
       #   myFile.write(str(invS[i,i]))
       #   myFile.write("\n")
 
+    def printMovies(self):
+        myFile=open("movieIDs.dat",'w')
+        myFile.truncate()
+
+        movies=self._matrix_reconstructed.get_col_labels()
+        for movie in movies :
+          myFile.write(str(movie)+",")
 
     def centroid(self, ids, is_row=True):
         points = []
diff --git a/recsys/algorithm/factorize.pyc b/recsys/algorithm/factorize.pyc
index 8ddc6846c4d74c0e30b323e23e17fcfaff1d0441..79f7199fc03ca842f12de55634fa87558ea13eb6 100644
GIT binary patch
delta 5927
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

delta 2297
zcmah~Yitx%6h8OP&TMzP+i7>{LSMGn0)>d$2Liz=NNGz@fdyMqEM@6T+ud%rv@=yO
zTME*KU<eX#OpHM24-*lIhRiPwhR9n$K;nZCkcaw1_@N1ji9bv{=kB!BL}Rx1%sJnA
z-gD2ry>*}6{EB&%he3bW;LpdrA1I+9126$v85l@87{%fXWkEwRm|0+GFv6kL4~7?U
z`ZKd7j(ir3XwVvvxDQ4cv<4;bmwZmfUM%t~UTWmShzhNFV4!tGszWl$RLO@WKTYye
zz$gHtP$ESzqCjgtMAuZ7IJ)|$hrQ(Z{Ik5Q*{RBF@v=kC?rA$1yX9mTHZk^-v$kly
zk3DeqR4iib3FrI_##oheVWz3DXPl?!gjM#UsDEl@*b4wzxHMc#aakuo)Gb&ztieD(
zqyZ8NBs{PaV8|;)6%v|c7$m%e8ywQu4h0H!Vv1C?RA&93lV`G%p+aL5<?=zopUIL7
zI~g!+_?k+p(Ap?2SCxr3s@uwKKgu0_$@XMylbN*QD1pQV`d)W2vA$+H%N9d5kB1A!
zwXx*5hd5JH9+`@Gw2PFq@xjeT%r+~tL}Tqdv7<I$uyvioQ&5rYiy7@1ZGFs+tsUH)
zG<$GDtOzQ>t1+DyGnLVIrlRw5MdckFHRDTmlxi@TyG7h3gEY%*4{&>A`mz;8+R@kB
z8@J=Bt}?sZEEA)PmqkeVs7$MWFm82Ma`EJn0xD75s4E%XMdf(V4iuHi<kT<~mrUM=
zAt<>G?x|s48=LUZ#t}}XAfiklii>zA5F7w$gXiSNQC&nEo`5tL@=hQ)AJQ!g8tHU#
zVvuVxd>N6uqCX?zoRQX^m;q^SWJAG(FA1Hp-XRWK7J{7(>EZFfq{MXfZPQ^$arc_W
zsl;0rPUDOsrT}_B+y4Ms@BjwpGk3fLfO>Qt@_-S<Vmz5jQjf_fSmtDdoJR)a9vp^5
zj=UZ!ovO8!rSXR7E|S9D>*#<G?Tx+|OLlWe10y%nsqx0;p{3?pVto&o5kdr$xSev@
zTtF)<7sYlC!$$ZYMyVPwreqMeqvVF{WH$xy5t5-@8+?bPIG!F|@O_#Jb_k4VV25!(
zT((QTNE9*ORfI`{Pt;_0=XU*F7Njd;(_q?LH=E*eL%H9kskFLdDE`q<s=bX;v|AK4
z&YU4N4Y8zUMym;}AX11#-u`GAiKC*Y@z5*#kljlo--HaX(BvQ28UB>ntb~;+8ftVN
zVgW?)ezE}RP|{G&XCZ~C&UGH)1>Cv6{7uGhGbgp8-=iI+Y=^}!D=)GS#j#bj*@p@E
zh{(s{_f?JB5n_*uy471X2if~f9ACYZMc#?7tr;U#Cy_xSTZn8YvWv*OMBXDp+lb0U
zju;zA%?+bmiOZs$Yd(zyihMrb^1yStre_r`38(>8kAzC+76-h6)y~2-S2#N@e9imV
z^UnLt^B6lTuC|nDXDI49@v!AB*5GVg7gDn&@-@Nd#s0QsXm_`*h+PnWwawDb5qwF^
z*|38(i=!LrO1~uFB#~1@=m4YnL}Y+!v46l)PorGpY;V7$V60DTyS>ul02jwQF0pUL
zZexTW;X*fG&HDi@<?H%dEIDXe(d!~@9^eiag`K|@{zzfvQGbV=d;~M6d=rDl#h$J_
z{uvjCx_-@Aj*<EI|85!7@V|j|k~@#Z?=g+CVk)htD(@z-qeyI=7cio?Way16Jbufx
z+k4Ge>LPbe^vqC8A9d+E`B0*$3`E7r&XtI={_+>jk~l<U8<7i0Y<i+M4Q4)ZTai*c
zdWG~?iBOj%Y{e7NZ^UT-L4Jpeih=r?|7`aS@{`SxZMse39U}MW2r8~2yBE`1xyWci
v9PS*SPuBvzfKSh_oK1$OM75P*pNNCj6}DgW+H*BJx9Bx7W`C{(BkTVL4nyK=

diff --git a/recsys/algorithm/matrix.py b/recsys/algorithm/matrix.py
index 85da209..b4fc51e 100644
--- a/recsys/algorithm/matrix.py
+++ b/recsys/algorithm/matrix.py
@@ -76,27 +76,68 @@ def __init__(self):
         self._values=None
         self._rows=None
         self._cols=None
+        self._additional_elements=[] #if no additional then len will equal 0
 
-#`nrows` and `ncols` specify the shape the resulting
+
+    def get_rows(self): #can use to get rated items and remove from recommendation
+        return self._rows
+
+    def get_cols(self): #can use to get rated items and remove from recommendation
+        return self._cols
+
+    def get_additional_elements(self):  # can use to get rated items and remove from recommendation
+        return self._additional_elements
+
+    # def create_blank(self,element_id,row_labels=None, col_labels=None):
+    #     if col_labels:
+    #
+    #     self._matrix = divisiSparseMatrix.from_named_lists(self._values, self._rows, self._cols,row_labels, col_labels)
+
+
+#row_labels specifies the row labels the complete matrix should have incase the inputted file doesn't include all indicies and it was saved in previous matrix (for update)
+#same explination for col_labels but for columns
 #matrix should have, in case it is larger than the largest index.
-    def create(self, data,row_labels=None, col_labels=None):
+    def create(self, data,row_labels=None, col_labels=None, foldin=False,truncate=False): #is_row is what I'm originally folding in
         self._values = map(itemgetter(0), data)
         self._rows = map(itemgetter(1), data)
         self._cols = map(itemgetter(2), data)
+
+        if foldin: #new to make sure not folding in user and item at same time
+            #idea: create matrix normally but keep track of the columns (items) or rows to be folded in before doing update
+            if col_labels: #if col_labels defined then I'm folding in a row
+                self._additional_elements = [x for x in self._cols if x not in col_labels]
+            else: #else I am folding in a column
+                self._additional_elements = [x for x in self._rows if x not in row_labels]
+            if truncate:
+                for item in self._additional_elements:
+                    if col_labels:
+                        index_remove = self._cols.index(item)
+                    else:
+                        index_remove = self._rows.index(item)
+                    del self._values[index_remove]
+                    del self._rows[index_remove]
+                    del self._cols[index_remove]
+
+
         self._matrix = divisiSparseMatrix.from_named_lists(self._values, self._rows, self._cols,row_labels, col_labels)
 
-    def update(self, matrix):
 
+
+
+    def update(self, matrix,is_batch=False): #isbatch is for creating the final sparse matrix ,since you will want to collect all then construct final matrix at end
+#To update the stored data matrix with the new values and create a new divisi spare matrix with it to retain the zeroes
         self._values.extend(matrix._values)
         self._rows.extend(matrix._rows)
         self._cols.extend(matrix._cols)
 
-        self._matrix = divisiSparseMatrix.from_named_lists(self._values, self._rows, self._cols)
+        if not is_batch:
+            self._matrix = divisiSparseMatrix.from_named_lists(self._values, self._rows, self._cols)
 
-    def squish(self,squishFactor):
+    def squish(self,squishFactor): #remove additional empty fields created by divisiSparseMatrix
         self._matrix=self._matrix.squish(squishFactor)
 
-
+    def index_sparseMatrix(self):
+        self._matrix = divisiSparseMatrix.from_named_lists(self._values, self._rows, self._cols)
 
     def empty(self):
         return not self._matrix or not self._matrix.values()
diff --git a/recsys/algorithm/matrix.pyc b/recsys/algorithm/matrix.pyc
index 0c5cba451931863960257260cb5c4e1e2c7a9d74..f4df053d8a20bc30468603f38761637e803476ba 100644
GIT binary patch
delta 1216
zcmZWo&1(}u6o0dyNlc~5CT(mLdQcl%TeTGwj7VFteqgOztWvOH&8~4{x@mW(8ZkxC
z9t5SRqu@Uvcoh$VBK7Fan-@{^;#G<lFM{t)Kc&pfZ-4vVoA=(lH?wf~%Ydo=Zc4m+
z^Y@kcy*Z%+z#y*ZnpkSK#4t;pa#ffIn9#wE)AUT5t_JfOOvEJDVV=PRY7J?b0N0dc
z3%D^!l3<VJ6XHNB%Z`ZQ)PZanpaD=sKLBHSh5<+NF)^Pi<ah>uijM30+^+>rwdhs7
zDKFq+F?F*kfjVFoi_;Jlqf6^Xf%uTBZsjp#Wseub+Rexoj?u5}-J~nks?i0h>siqq
z;jJtCHiwKZp(;`kfRzlyI4=4Q;fjWs7*C(R)6gKyv4#raU06O<MT6s)b1-K>1E&ht
zO$aB6Pbx5j1dpdf(7kG{ZnRdU^}|(bB+|N}Q;>R*ek(d|I1h6s`3}dwNtp%}jAg<-
z26Hh8(o(4YC#0<kG-7a+n7YO^$!6Ze#hDxn0JsU2n0S%yID-wP?r3=05g!43YLrg}
z$x|n|iZJC&+a$5O2&f%4)!n87bGl7_;CgkztiDkypH20%w-B@uWDz*YuGXrqAIL89
za5gA8+~X9k&MCgny(yj~Lf(p@H&wggiBGw50aJvi0|8i$hGHlN>s7j$!J2Vpl{8D>
z$fzaCmKOPw9W0B_R$)&=fv5x+1q(}+kvEenC98A~3xzE(AOO*q>F(;s&Gtb8I$NIn
zEg#83CbRdW;uVLNu8E>q5??b3>keund2<yD@2-XUi9eZ-`cahM$IEDqt!1=}oM2og
zqp#*bnP{+*2|mO1CnJlwp3;UEh)2e3YZ~1x7c81~!pJ-8@X)V6z?j>`kL=5H3zXUN
z{T|6T;g7wWpp(EP=q1=ku%CePls!G+R3p!(e%J*9I*NTvyluJFLNN^%kHu9S$!vQv
zPD~86K8+8O75T*nJHwuc(cEn|B$jl~kglG+KC~^(*mH5ZZKr7ukZ}@SPV2PqvG~>2
V!}`UJ_DMD<7F#OfXL~0*`41yl<5vIx

delta 522
zcmX9)Jxc>Y5S_hb?{cSNk~1I1=tj|a2r3CEf;K8DS_obm5fMW!K_!srDG0GltIhaF
z{HTJZh5iOXEG)!MENz^*u=^hG-p;(8y|?9?1tav^XZ`Je-WeZJD+B;51b-2KqV}o;
zkG6oe276)fG-w0tkrN7p4qiAA2Dl|hr?!e9VsOqX!7t)fb`Y;)>0S?@<nY<HV_+w?
zgr{~LTX8EFArFKtOj?yNX$GeS0Y2dCz?M9haH8W^A}0(qlS3kc6G?@oWHG_jE|<GK
zOc|yG?x>p6%aY)CZ%7MX6^$GQ_XRf`;ALtFJE>88PfcZJ$aZI$B$30F-E<p&WNnBq
zx@dak^Xe|CZn9?6^tOig=@(ff_YhAr&xI<Vb32G_hbM6JOp{CoQ!sciV}U=5xS^UU
zQm{$zEh1f*k$&Bbm=f*mtv*MVYoHov=t06Ujt#fOJik?PPW{0gi3`!dtQxoIhXJgY
f>wXp2)S2YoBYaZRVg|p}L6GGDKM5amd7=IR*r`^h

diff --git a/recsys/datamodel/data.py b/recsys/datamodel/data.py
index 21de618..f2f3d94 100644
--- a/recsys/datamodel/data.py
+++ b/recsys/datamodel/data.py
@@ -121,7 +121,7 @@ def load(self, path, force=True, sep='\t', format=None, pickle=False):
             self._load_pickle(path)
         else:
             i = 0 
-            for line in codecs.open(path, 'r', 'utf8'):
+            for line in codecs.open(path, 'r', 'ISO-8859-1'): #was utf8 changed it to 'ISO-8859-1'
                 data = line.strip('\r\n').split(sep)
                 value = None
                 if not data:
diff --git a/recsys/datamodel/data.pyc b/recsys/datamodel/data.pyc
index 57a97e63b45c10a3996f3fd0a234824a80e45c99..176bf003ceacb312e03caca2d384a9ec88644cf4 100644
GIT binary patch
delta 269
zcmbR4xX_WE`7<xqvXkF8vKx!?7jrQ%Fn9+0>snZtTIw2Zb{4(Jtl|I^3vvPx#vsB3
zM6d!04JHta4Mf;XR+LX+G}=5@GLXr=gas&6T9Rf_%m`#~f=Mnv4PKB!uois~%K$_e
z0*MkHAQ4}jSe6=JkeQsFlM1zFvZ0I(n+-^n!{%a{7Dhd1Alpv^WO$GRkN{hgQkq);
sb`02A=q{SvDc39jcDWHqmlud|oy@C{F}X$FlpE{}u$LSrACbQd0QkEy3jhEB

delta 211
zcmZ4JINgz*`7<xqubYoIvKxzXmas4|FqD?0S#0(cy~wQM2;>Dhg9sB4VG1HxfrJJV
zh{XmXY$tDEO=b!*-n>{ckcl-&3n(`Eue1cW0Z7siL>Nt0kP&5r7%<sd#)i!nB<i@i
zR;Gnf&jrZ#(*W5T1hNcle@ba?0oWR_h3Ixoo+Q`826mJ2WEuJ85R1U3J5D|)e;EJ?
CoGHZs


From 9c1d42d6b9d40b45212c735509d08cdc82b86995 Mon Sep 17 00:00:00 2001
From: Ibrahim Abou Elseoud <Ibrahim.elseoud@gmail.com>
Date: Thu, 8 Jun 2017 14:34:36 +0200
Subject: [PATCH 3/7] Cleaned up code for commiting to github, on a much much
 earlier date, I added the ability to recommend items unrated by the user for
 folded-in users even if we loaded the SVD model instead of built it from
 scratch which wasn't previously allowed under original implementation

---
 AUTHORS                        |   1 +
 CHANGELOG                      |   5 +
 recsys/algorithm/factorize.py  | 406 +++++++++++++++++++++------------
 recsys/algorithm/factorize.pyc | Bin 30045 -> 32828 bytes
 recsys/algorithm/matrix.py     |  10 +-
 recsys/datamodel/data.py       | 178 ++++++++++++++-
 recsys/datamodel/data.pyc      | Bin 8353 -> 12984 bytes
 7 files changed, 437 insertions(+), 163 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 8003cab..eb97b49 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1 +1,2 @@
 Oscar Celma (ocelma __at__ gmail __dot__ com), http://ocelma.net
+Ibrahim Abou Elseoud (Ibrahim__dot__Elseoud__at__ gmail __dot__ com), for updating SVD model part
diff --git a/CHANGELOG b/CHANGELOG
index f15b79f..ad6bba0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -28,3 +28,8 @@ See: https://github.com/ocelma/python-recsys/commits/master
 
 2011-10-08
     * Added the whole project at github
+
+2017-06-08
+    * Added updating the SVD model incrementally by folding-in
+    * Added a capability to split the dataset for train,test,foldin to facilitate testing the Fold-in implementation
+    * Added update to github
diff --git a/recsys/algorithm/factorize.py b/recsys/algorithm/factorize.py
index c5ddb53..75e893f 100644
--- a/recsys/algorithm/factorize.py
+++ b/recsys/algorithm/factorize.py
@@ -26,20 +26,24 @@
 from divisi2 import DenseVector
 from divisi2 import DenseMatrix
 from divisi2.ordered_set import OrderedSet
-                                        
+
 from recsys.algorithm.baseclass import Algorithm
 from recsys.algorithm.matrix import SimilarityMatrix
 from recsys.algorithm import VERBOSE
 
 from numpy.linalg import inv #for update
 import numpy as np
+from divisi2.sparse import SparseMatrix as divisiSparseMatrix
+from divisi2.sparse import SparseVector as divisiSparseVector
+from divisi2.dense import DenseVector as divisiDenseVector
+
 from recsys.datamodel.data import Data
 
 TMPDIR = '/tmp'
 
 class SVD(Algorithm):
     """
-    Inherits from base class Algorithm. 
+    Inherits from base class Algorithm.
     It computes SVD (Singular Value Decomposition) on a matrix *M*
 
     It also provides recommendations and predictions using the reconstructed matrix *M'*
@@ -72,6 +76,8 @@ def __init__(self, filename=None):
         self._file_col_ids = None
 
         #Update feature
+        self._foldinZeroes={}
+        self.inv_S=None #since it doesn't get updated so redundent to calculate each time
 
     def __repr__(self):
         try:
@@ -124,7 +130,7 @@ def load_model(self, filename):
                 idx = [ int(idx.strip()) for idx in zip.read('.row_ids').split('\n') if idx]
             except:
                 idx = [ idx.strip() for idx in zip.read('.row_ids').split('\n') if idx]
-            #self._U = DenseMatrix(vectors) 
+            #self._U = DenseMatrix(vectors)
             self._U = DenseMatrix(vectors, OrderedSet(idx), None)
         try:
             self._V = loads(zip.read('.V'))
@@ -140,7 +146,7 @@ def load_model(self, filename):
                 idx = [ int(idx.strip()) for idx in zip.read('.col_ids').split('\n') if idx]
             except:
                 idx = [ idx.strip() for idx in zip.read('.col_ids').split('\n') if idx]
-            #self._V = DenseMatrix(vectors) 
+            #self._V = DenseMatrix(vectors)
             self._V = DenseMatrix(vectors, OrderedSet(idx), None)
 
         self._S = loads(zip.read('.S'))
@@ -148,7 +154,7 @@ def load_model(self, filename):
         # Shifts for Mean Centerer Matrix
         self._shifts = None
         if '.shifts.row' in zip.namelist():
-            self._shifts = [loads(zip.read('.shifts.row')), 
+            self._shifts = [loads(zip.read('.shifts.row')),
                             loads(zip.read('.shifts.col')),
                             loads(zip.read('.shifts.total'))
                            ]
@@ -198,7 +204,7 @@ def save_model(self, filename, options={}):
         # Store Options in the ZIP file
         fp.write(filename=filename + '.config', arcname='README')
         os.remove(filename + '.config')
-        
+
         # Store matrices in the ZIP file
         for extension in ['.U', '.S', '.V']:
             fp.write(filename=filename + extension, arcname=extension)
@@ -231,6 +237,7 @@ def _reconstruct_matrix(self, shifts=None, force=True):
                 self._matrix_reconstructed = divisi2.reconstruct(self._U, self._S, self._V)
         return self._matrix_reconstructed
 
+
     def compute(self, k=100, min_values=None, pre_normalize=None, mean_center=False, post_normalize=True, savefile=None):
         """
         Computes SVD on matrix *M*, :math:`M = U \Sigma V^T`
@@ -251,7 +258,7 @@ def compute(self, k=100, min_values=None, pre_normalize=None, mean_center=False,
         super(SVD, self).compute(min_values) #creates matrix and does squish to not have empty values
 
         if VERBOSE:
-            sys.stdout.write('Computing svd k=%s, min_values=%s, pre_normalize=%s, mean_center=%s, post_normalize=%s\n' 
+            sys.stdout.write('Computing svd k=%s, min_values=%s, pre_normalize=%s, mean_center=%s, post_normalize=%s\n'
                             % (k, min_values, pre_normalize, mean_center, post_normalize))
             if not min_values:
                 sys.stdout.write('[WARNING] min_values is set to None, meaning that some funky recommendations might appear!\n')
@@ -264,17 +271,18 @@ def compute(self, k=100, min_values=None, pre_normalize=None, mean_center=False,
         if mean_center:
             if VERBOSE:
                 sys.stdout.write("[WARNING] mean_center is True. svd.similar(...) might return nan's. If so, then do svd.compute(..., mean_center=False)\n")
-            matrix, row_shift, col_shift, total_shift = matrix.mean_center() 
+            matrix, row_shift, col_shift, total_shift = matrix.mean_center()
             self._shifts = (row_shift, col_shift, total_shift)
 
+
         # Pre-normalize input matrix?
         if pre_normalize:
             """
-            Divisi2 divides each entry by the geometric mean of its row norm and its column norm. 
+            Divisi2 divides each entry by the geometric mean of its row norm and its column norm.
             The rows and columns don't actually become unit vectors, but they all become closer to unit vectors.
             """
             if pre_normalize == 'tfidf':
-                matrix = matrix.normalize_tfidf() #TODO By default, treats the matrix as terms-by-documents; 
+                matrix = matrix.normalize_tfidf() #TODO By default, treats the matrix as terms-by-documents;
                                                   # pass cols_are_terms=True if the matrix is instead documents-by-terms.
             elif pre_normalize == 'rows':
                 matrix = matrix.normalize_rows()
@@ -296,7 +304,7 @@ def compute(self, k=100, min_values=None, pre_normalize=None, mean_center=False,
             options = {'k': k, 'min_values': min_values, 'pre_normalize': pre_normalize, 'mean_center': mean_center, 'post_normalize': post_normalize}
             self.save_model(savefile, options)
 
-    def _get_row_reconstructed(self, i, zeros=None):
+    def _get_row_reconstructed(self, i, zeros=None): #if foldin that means it is known what the user rated and zeros contains the rated items
         if zeros:
             return self._matrix_reconstructed.row_named(i)[zeros]
         return self._matrix_reconstructed.row_named(i)
@@ -306,6 +314,40 @@ def _get_col_reconstructed(self, j, zeros=None):
             return self._matrix_reconstructed.col_named(j)[zeros]
         return self._matrix_reconstructed.col_named(j)
 
+    def _get_row_unrated(self,i,rated): # use for foldin since that means users new rated items are known so no need to squish or need normal matrix
+        sparse_matrix = self._matrix_reconstructed.row_named(i).to_sparse()
+        # values: np array with the predicted ratings or ratings
+        # named_rows: normal array with movie names
+        values, named_cols = sparse_matrix.named_lists() #values contains a np array with predicted ratings , while named_cols contains list of labels of columns
+        removal_indicies = [] #array of indicies for removal
+
+        for item in rated:
+            index_remove = named_cols.index(item)
+            del named_cols[index_remove] #since its a normal list can remove like this
+            removal_indicies.append(index_remove)
+
+        values = np.delete(values, removal_indicies) #since it's a numpy array so must remove like this
+
+        return divisiSparseVector.from_named_lists(values, named_cols).to_dense()
+
+
+
+    def _get_col_unrated(self, j,rated):  # use for foldin since that means users new rated items are known so no need to squish or need normal matrix
+        sparse_matrix=self._matrix_reconstructed.col_named(j).to_sparse()
+        # values: np array with the predicted ratings or ratings
+        # named_rows: normal array with movie names
+        values, named_rows= sparse_matrix.named_lists()
+        removal_indicies=[]
+
+        for item in rated:
+            index_remove = named_rows.index(item)
+            del named_rows[index_remove]
+            removal_indicies.append(index_remove)
+
+        values=np.delete(values, removal_indicies)
+
+        return divisiSparseVector.from_named_lists(values,named_rows).to_dense()
+
     def predict(self, i, j, MIN_VALUE=None, MAX_VALUE=None):
         """
         Predicts the value of :math:`M_{i,j}`, using reconstructed matrix :math:`M^\prime = U \Sigma_k V^T`
@@ -347,25 +389,79 @@ def recommend(self, i, n=10, only_unknowns=False, is_row=True):
             self.compute() #will use default values!
         item = None
         zeros = []
-        if only_unknowns and not self._matrix.get():
+        seeDict=False
+        if only_unknowns and not self._matrix.get() and len(self._foldinZeroes)==0:
             raise ValueError("Matrix is empty! If you loaded an SVD model you can't use only_unknowns=True, unless svd.create_matrix() is called")
+        if not self._matrix.get():
+            seeDict=True
         if is_row:
             if only_unknowns:
-                zeros = self._matrix.get().row_named(i).zero_entries()
-            item = self._get_row_reconstructed(i, zeros)
+                if seeDict:
+                    zeros=self._foldinZeroes[i] #zeros in this instance contains the rated items
+                    if len(zeros)==0:
+                        raise ValueError("Matrix is empty! If you loaded an SVD model you can't use only_unknowns=True, unless svd.create_matrix() is called or youve just folded them in")
+                    else:
+                        item = self._get_row_unrated(i, zeros) #removing the rated items from utility row for recommendations
+                else:
+                    zeros = self._matrix.get().row_named(i).zero_entries()
+                    item = self._get_row_reconstructed(i, zeros)
+            else:
+                item = self._get_row_reconstructed(i, zeros)
         else:
             if only_unknowns:
-                zeros = self._matrix.get().col_named(i).zero_entries()
-            item = self._get_col_reconstructed(i, zeros)
+                if seeDict:
+                    zeros=self._foldinZeroes[i] #zeros in this instance contains the rated items
+                    if len(zeros)==0:
+                        raise ValueError("Matrix is empty! If you loaded an SVD model you can't use only_unknowns=True, unless svd.create_matrix() is called or you just folded them in")
+                    else:
+                        item = self._get_col_unrated(i, zeros)  #removing the rated items from utility columns for recommendations
+                else:
+                    zeros = self._matrix.get().col_named(i).zero_entries()
+                    item = self._get_col_reconstructed(i, zeros)
+            else:
+                item = self._get_row_reconstructed(i, zeros)
+
         return item.top_items(n)
 
-    def load_updateDataTuple_foldin(self, filename, force=True, sep='\t', format={'value':0, 'row':1, 'col':2}, pickle=False,is_row=True,truncate=False):
+    def _calc_mean_center(self, matrix, is_row=True):  #created this to use the loaded shifts and calculate the row or column shift
+        row_shift, col_shift, total_shift = self._shifts
+
+        total_mean = total_shift  # use the global shift one
+        if is_row:
+            row_means = matrix.row_op(np.mean) - total_mean  # calculate row shift
+            col_means = col_shift  # use already given col shifts
+        else:
+            row_means = row_shift  # use already given row shifts
+            col_means = matrix.col_op(np.mean) - total_mean  # calculate col shifts
+
+        row_lengths = matrix.row_op(len)
+        col_lengths = matrix.col_op(len)
+
+        shifted = matrix.copy()
+        for row, col in shifted.keys():
+            shifted[row, col] -= (
+                                     (row_means[row] * row_lengths[row]
+                                      + col_means[col] * col_lengths[col]
+                                      ) / (row_lengths[row] + col_lengths[col])
+                                 ) + total_mean
+
+        return (shifted, row_means, col_means, total_mean)
+        # return shifted
+
+    def load_updateDataTuple_foldin(self, filename,force=True, sep='\t', format={'value':0, 'row':1, 'col':2}, pickle=False,is_row=True,truncate=True,post_normalize=False):
         """
-        Loads a dataset file that contains a SINGLE tuple (a dataset for a single user OR item , has to be either same row or same column depending on is_row aka tuple)
+        Folds-in a SINGLE user OR item. First loads a dataset file that contains a SINGLE tuple (a dataset for a single user OR item , has to be either same row or same column depending on is_row aka tuple)
+
+        For params: filename,force,sep,format,pickle then see params definition in *datamodel.Data.load()*
+
+        :param is_row: are you trying to foldin a row or a column ? yes->foldin row , no->foldin column
+        :type is_row: boolean
+        :param truncate: sometimes new users rate new items not in the original SVD matrix so would you like new items to be truncated or folded in ? default is foldin
+        :type truncate: boolean
+        :param post_normalize: Normalize every row of :math:`U \Sigma` to be a unit vector. Thus, row similarity (using cosine distance) returns :math:`[-1.0 .. 1.0]`
+        :type post_normalize: Boolean
 
-        See params definition in *datamodel.Data.load()*
         """
-        # nDimension
         if force:
             self._updateData = Data()
 
@@ -379,11 +475,14 @@ def load_updateDataTuple_foldin(self, filename, force=True, sep='\t', format={'v
             print type(nDimensionLabels[0])
             print len(nDimensionLabels)
             self._singleUpdateMatrix.create(self._updateData.get(), col_labels=nDimensionLabels, foldin=True,truncate=truncate)
+            self._foldinZeroes[self._singleUpdateMatrix.get_rows()[0]] = self._singleUpdateMatrix.get_cols()
+
 
         else:
             nDimensionLabels = self._U.all_labels() #get labels from U matrix to complete the sparse matrix
             print nDimensionLabels
             self._singleUpdateMatrix.create(self._updateData.get(), row_labels=nDimensionLabels, foldin=True,truncate=truncate)
+            self._foldinZeroes[self._singleUpdateMatrix.get_cols()[0]] = self._singleUpdateMatrix.get_rows()
 
         if not truncate:
             additionalElements=self._singleUpdateMatrix.get_additional_elements()
@@ -402,23 +501,49 @@ def load_updateDataTuple_foldin(self, filename, force=True, sep='\t', format={'v
         # #update the data matrix
         if VERBOSE:
             print "updating the sparse matrix"
-        # print "matrix before update:",self._matrix.get().shape
         if self._matrix.get(): #if matrix not there due to load ignore it
             self._matrix.update(self._singleUpdateMatrix) # updating the data matrix for the zeroes , also for saving the data matrix if needed
-        # print "matrix after update:",self._matrix.get().shape
-        self._update(is_row=is_row)
+
+        # Mean centering
+        if self._shifts: #if not None then it means mean_center was equal true
+            row_shift, col_shift, total_shift=self._shifts
+
+
+            meanedMatrix, rowShift, colShift, totalShift=self._calc_mean_center(self._singleUpdateMatrix.get(),is_row=is_row)
+
+            self._singleUpdateMatrix.set(meanedMatrix)
+
+            if is_row:
+                values, named_rows = row_shift.to_sparse().named_lists() #values numpy array, named_rows normal array
+                valuesFold, named_rowsFold = rowShift.to_sparse().named_lists()
+
+            else:
+                values, named_rows = col_shift.to_sparse().named_lists()  # values numpy array, named_rows normal array
+                valuesFold, named_rowsFold = colShift.to_sparse().named_lists()
+
+
+            values=np.concatenate((values, valuesFold))
+            named_rows.extend(named_rowsFold)
+
+            if is_row:
+                row_shift=divisiSparseVector.from_named_lists(values, named_rows).to_dense()
+            else:
+                col_shift=divisiSparseVector.from_named_lists(values, named_rows).to_dense()
+
+            self._shifts=(row_shift, col_shift, total_shift)
+
+
+        self._update(is_row=is_row,post_normalize=post_normalize)
 
     def _construct_batch_dictionary(self,data,is_row=True):
-        '''
-        
+        """
+
         :param data: Data()
         :param is_row: Boolean
         :return: constructs a dictionary with the row or col as the keys (depending on which is being added) with values as the tuples
         in self._batchDict
-        '''
-        # self._values = map(itemgetter(0), data)
-        # self._rows = map(itemgetter(1), data)
-        # self._cols = map(itemgetter(2), data)
+        """
+
         key_idx=1 #key index default is the row
         if not is_row:
             key_idx=2
@@ -436,23 +561,33 @@ def _construct_batch_dictionary(self,data,is_row=True):
         print "Batch loaded successfully"
 
 
-    def load_updateDataBatch_foldin(self, filename, force=True, sep='\t', format={'value': 0, 'row': 1, 'col': 2},
-                                 pickle=False, is_row=True,truncate=False):
+    def load_updateDataBatch_foldin(self, filename=None, data=None, force=True, sep='\t', format={'value': 0, 'row': 1, 'col': 2},
+                                 pickle=False, is_row=True,truncate=True,post_normalize=False):
             """
-            Dont forget future work in presentation , remove old and insert new
-            Loads a dataset file that contains Multiple tuples
+            Folds in the batch users or items, first Loads a dataset file that contains Multiple tuples (users or items) or uses the preloaded data from the datamodel/data.py object then folds them in with their ratings
+
+            :param data: Contains the dataset that was loaded using the Data() class
+            :type data: Data()
 
-            truncate:boolean-> sometimes new users rate new items not in the original SVD matrix so would you like new items to be truncated or folded in ? default is foldin
-            is_row: boolean -> are you trying to foldin a row or a column ? yes->foldin row , no->foldin column
-            See params definition in *datamodel.Data.load()*
-            
+            For params: filename,force,sep,format,pickle then see params definition in *datamodel.Data.load()*
+
+            :param is_row: are you trying to foldin a row or a column ? yes->foldin row , no->foldin column
+            :type is_row: boolean
+            :param truncate: sometimes new users rate new items not in the original SVD matrix so would you like new items to be truncated or folded in ? default is foldin
+            :type truncate: boolean
+            :param post_normalize: Normalize every row of :math:`U \Sigma` to be a unit vector. Thus, row similarity (using cosine distance) returns :math:`[-1.0 .. 1.0]`
+            :type post_normalize: Boolean
             """
-            # call update here until it finishes
-            # nDimension
+
             if force:
                 self._updateData = Data()
-
-            self._updateData.load(filename, force, sep, format, pickle) #load array of tuples
+            if filename: #not null
+                self._updateData.load(filename, force, sep, format, pickle) #load array of tuples
+            else:
+                if data:
+                    self._updateData =data
+                else:
+                    raise ValueError('No data or filename set!')
             print "Reading the new batch"
 
             self._construct_batch_dictionary(self._updateData.get(),is_row)
@@ -461,32 +596,32 @@ def load_updateDataBatch_foldin(self, filename, force=True, sep='\t', format={'v
             nDimensionLabels=None
             if (is_row):
                 nDimensionLabels = self._V.all_labels()[0]  # get labels from V matrix to complete the sparse matrix
-                # print nDimensionLabels
             else:
                 nDimensionLabels = self._U.all_labels()[0]  # get labels from U matrix to complete the sparse matrix
-                # print nDimensionLabels
             length_of_dict=len(self._batchDict)
             i=0
+            meanDenseVector=[]
             isbatch=True
             for key_idx in self._batchDict: #data in batchDict in form {key:[(tuple)]}
-                print "user:",key_idx
                 i += 1
+                if VERBOSE:
+                    if i % 100 == 0:
+                        sys.stdout.write('.')
+                    if i % 1000 == 0:
+                        sys.stdout.write('|')
+                    if i % 10000 == 0:
+                        sys.stdout.write(' (%d K user)\n' % int(i / 1000))
+
                 if (is_row):
                     self._singleUpdateMatrix.create(self._batchDict[key_idx], col_labels=nDimensionLabels,foldin=True,truncate=truncate)
 
                 else:
                     self._singleUpdateMatrix.create(self._batchDict[key_idx], row_labels=nDimensionLabels,foldin=True,truncate=truncate)
 
-                # if(i==length_of_dict):
-                #     isbatch=False
-
-
                 # If it's trying to foldin a new user who has rated a new item which was not used before, then foldin the item first then foldin that user
                 if not truncate:
                     additionalElements = self._singleUpdateMatrix.get_additional_elements()
-                    print "dimension", len(nDimensionLabels)
-                    print "additional elements:", additionalElements
-                    print "length", len(additionalElements)
+
                     if len(additionalElements) != 0:
                         for item in additionalElements:
                             if (is_row):  # if I am folding in a row then , the additionals added that shouldn't be are the columns to be folded in to the rows
@@ -495,24 +630,54 @@ def load_updateDataBatch_foldin(self, filename, force=True, sep='\t', format={'v
                             else:
                                 self._singleAdditionalFoldin.create([(0, item, nDimensionLabels[0])],
                                                                     col_labels=self._V.all_labels()[0])
+
                             self._update(update_matrix=self._singleAdditionalFoldin, is_row=not is_row)
 
+                if self._shifts:  # if not None then it means mean_center was equal true
+                    row_shift, col_shift, total_shift = self._shifts
+
+
+                    meanedMatrix, rowShift, colShift, totalShift = self._calc_mean_center(self._singleUpdateMatrix.get(),is_row=is_row)
+
+                    self._singleUpdateMatrix.set(meanedMatrix)
+                    # row shift cause it's row for the time being
+                    if is_row:
+                        meanDenseVector.append(rowShift)
+
+                    else:
+                        meanDenseVector.append(colShift)
+
 
-                # #update the data matrix
-                print "updating the sparse matrix"
-                # print "matrix before update:",self._matrix.get().shape
                 if self._matrix.get(): #if matrix not there due to load ignore it
                     self._matrix.update(
                         self._singleUpdateMatrix,is_batch=isbatch)  # updating the data matrix for the zeroes , also for saving the data matrix if needed
-                # print "matrix after update:",self._matrix.get().shape
+
                 self._update(is_row=is_row,is_batch=isbatch) #Do foldin on the singleUpdateMatrix tuple
+            if VERBOSE:
+                sys.stdout.write('\n')
+            #     UPDATING MEAN CENTER PART
+            if self._shifts:
+                sys.stdout.write("updating shifts")
+                if is_row:
+                    values, named_rows = row_shift.to_sparse().named_lists()  # values numpy array, named_rows normal array
+                else:
+                    values, named_rows = col_shift.to_sparse().named_lists()  # values numpy array, named_rows normal array
+                for vector in meanDenseVector:
+                    valuesFold, named_rowsFold = vector.to_sparse().named_lists()  # rowShift contains new calculated row shift
+                    values = np.concatenate((values, valuesFold))
+                    named_rows.extend(named_rowsFold)
+                if is_row:
+                    row_shift = divisiSparseVector.from_named_lists(values, named_rows).to_dense()
+                else:
+                    col_shift = divisiSparseVector.from_named_lists(values, named_rows).to_dense()
 
-            self.update_sparse_matrix_data(is_batch=True)
+                self._shifts = (row_shift, col_shift, total_shift)
 
+            self.update_sparse_matrix_data(is_batch=True,squish=False,post_normalize=post_normalize)
 
-    def update_sparse_matrix_data(self,squishFactor=10,is_batch=False):
+
+    def update_sparse_matrix_data(self,squishFactor=10,is_batch=False,squish=True,post_normalize=False):
         #update the data matrix
-        # print "matrix before update:",self._matrix.get().shape
         if is_batch:
             if self._matrix.get():
                 if VERBOSE:
@@ -521,48 +686,34 @@ def update_sparse_matrix_data(self,squishFactor=10,is_batch=False):
             if VERBOSE:
                 print "before updating, M=", self._matrix_reconstructed.shape
             # Sim. matrix = U \Sigma^2 U^T
-            self._reconstruct_similarity(post_normalize=False, force=True)
+            self._reconstruct_similarity(post_normalize=post_normalize, force=True)
             # M' = U S V^t
             self._reconstruct_matrix(shifts=self._shifts, force=True)
             if VERBOSE:
                 print "done updating, M=", self._matrix_reconstructed.shape
+        if squish:
+            if self._matrix.get(): #if loaded model there is no matrix
+                if VERBOSE:
+                    print "commiting the sparse data matrix by removing empty rows and columns divisi created"
+                self._matrix.squish(squishFactor) # updating the data matrix for the zeroes ,#NOTE: Intensive so do at end
 
-        if self._matrix.get(): #if loaded model there is no matrix
-            if VERBOSE:
-                print "commiting the sparse data matrix by removing empty rows and columns divisi created"
-            self._matrix.squish(squishFactor) # updating the data matrix for the zeroes ,#NOTE: Intensive so do at end
-            # print "matrix after update:",self._matrix.get().shape
-
-
-    def _update(self,update_matrix=None,is_row=True,is_batch=False): #update(tuple:denseVector tuple,isRow=True,,
-      if VERBOSE:
-          print "type of S",type(self._S)
-          print "type of U",type(self._U)
-          print "type of V",type(self._V)
-          print "type of data",type(self._data)
-          print "type of matrix",type(self._matrix)
-          print "type of matrix reconstructed",type(self._matrix_reconstructed)
-          print "type of matrix similarity",type(self._matrix_similarity)
-
-          print "dimensions of S",self._S.shape
-          print "dimensions of U",self._U.shape
-          print "dimensions of V",self._V.shape
-
-      invS=np.zeros((self._S.shape[0], self._S.shape[0]))
-      for i in range(self._S.shape[0]):
-          # invS[i, i] = self._S[i]  # creating diagonal matrix
-          invS[i, i] = self._S[i]**-1  # creating diagonal matrix and inverting using special property of diagonal matrix
-      # invS=inv(invS) inverting with numpy
+
+    def _update(self,update_matrix=None,is_row=True,is_batch=False,post_normalize=False):
+    #The function which does the actual folding-in process
+      if self.inv_S is None:
+          self.inv_S=np.zeros((self._S.shape[0], self._S.shape[0]))
+          for i in range(self._S.shape[0]):
+              self.inv_S[i, i] = self._S[i]**-1  # creating diagonal matrix and inverting using special property of diagonal matrix
 
       #if new is row -> V*S^-1
       if is_row:
-        prodM=self._V.dot(invS)
-        if VERBOSE:
-            print "dimension of VxS^-1=", prodM.shape
+        prodM=self._V.dot(self.inv_S)
+        # if VERBOSE:
+        #     print "dimension of VxS^-1=", prodM.shape
       else:       #if new is col -> U*S^-1
-        prodM = self._U.dot(invS)
-        if VERBOSE:
-            print "dimension of UxS^-1=", prodM.shape
+        prodM = self._U.dot(self.inv_S)
+        # if VERBOSE:
+        #     print "dimension of UxS^-1=", prodM.shape
 
       if update_matrix:
           updateTupleMatrix=update_matrix.get()
@@ -571,74 +722,30 @@ def _update(self,update_matrix=None,is_row=True,is_batch=False): #update(tuple:d
 
       if not is_row:
           updateTupleMatrix=updateTupleMatrix.transpose() #transpose
-      if VERBOSE:
-          print "dimensions of user",updateTupleMatrix.shape
+
       res=updateTupleMatrix.dot(prodM)
-      if VERBOSE:
-          print "type of res=", type(res)
-          print "dimension of resultant is", res.shape
 
       if is_row:
-      #use new value can now be concatinated with U
-        if VERBOSE:
-            print "U before adding", self._U.shape
+      #new value can now be concatinated with U
+
         self._U=self._U.concatenate(res)
-        if VERBOSE:
-            print "U after adding", self._U.shape
 
       else:
-        if VERBOSE:
-            print "V before adding", self._V.shape
+      #new value can now be concatinated with V
+
         self._V = self._V.concatenate(res)
-        if VERBOSE:
-            print "V after adding", self._V.shape
 
-     #TODO: contemplating removing this segment and just reconstruct in the updating spare matrix function
       if not is_batch: #will reconstruct all at end with batch using another function
         if VERBOSE:
             print "before updating, M=",self._matrix_reconstructed.shape
         # Sim. matrix = U \Sigma^2 U^T
-        self._reconstruct_similarity(post_normalize=False, force=True)
+        self._reconstruct_similarity(post_normalize=post_normalize, force=True)
         # M' = U S V^t
         self._reconstruct_matrix(shifts=self._shifts, force=True)
         if VERBOSE:
             print "done updating, M=",self._matrix_reconstructed.shape
 
 
-
-
-      # myFile=open("prodMVSq.dat",'w')
-      # myFile.truncate()
-      #
-      # for i in range(20):
-      #   myFile.write(str(res[0, i])+" ")
-      #
-      #   myFile.write("\n")
-
-      # # invS = inv(diag_S)
-      # # print "dimensions of S^-1", invS.shape
-      #
-      #
-      # print "writing s to file"
-      # myFile=open("invS.dat",'w')
-      # myFile.truncate()
-      # # for item in self.invS.tolist():
-      # #     myFile.write(str(item))
-      # #     myFile.write("\n")
-      # myFile.write("dimensions= "+str(invS.shape))
-      # myFile.write("\n")
-      # for i in range(invS.shape[0]):
-      #   myFile.write(str(invS[i,i]))
-      #   myFile.write("\n")
-
-    def printMovies(self):
-        myFile=open("movieIDs.dat",'w')
-        myFile.truncate()
-
-        movies=self._matrix_reconstructed.get_col_labels()
-        for movie in movies :
-          myFile.write(str(movie)+",")
-
     def centroid(self, ids, is_row=True):
         points = []
         for id in ids:
@@ -684,7 +791,7 @@ def kmeans(self, ids, k=5, components=3, are_rows=True):
         i = 0
         clusters = dict()
         for cluster in labels:
-            if not clusters.has_key(cluster): 
+            if not clusters.has_key(cluster):
                 clusters[cluster] = dict()
                 clusters[cluster]['centroid'] = centroids[cluster]
                 clusters[cluster]['points'] = []
@@ -754,7 +861,7 @@ def similar_neighbours(self, i, j, Sk=10):
                 _Sk += 1
             current += 1
             _Sk -= 1
-            if _Sk == 0: 
+            if _Sk == 0:
                 break # We have enough elements to use
         return similars[:Sk]
 
@@ -816,7 +923,7 @@ def predict(self, i, j, Sk=10, weighted=True, MIN_VALUE=None, MAX_VALUE=None):
 # SVDNeighbourhoodKoren
 class __SVDNeighbourhoodKoren(SVDNeighbourhood):
     """
-    Inherits from SVDNeighbourhood class. 
+    Inherits from SVDNeighbourhood class.
 
     Neighbourhood model, using Singular Value Decomposition.
     Based on 'Factorization Meets the Neighborhood: a Multifaceted
@@ -901,7 +1008,7 @@ def predict(self, i, j, Sk=None, MIN_VALUE=None, MAX_VALUE=None):
         Predicts the value of *M(i,j)*
 
         It is based on 'Factorization Meets the Neighborhood: a Multifaceted
-        Collaborative Filtering Model' (Yehuda Koren). 
+        Collaborative Filtering Model' (Yehuda Koren).
         Equation 3 (section 2.2):
 
         :math:`\hat{r}_{ui} = b_{ui} + \\frac{\sum_{j \in S^k(i;u)} s_{ij} (r_{uj} - b_{uj})}{\sum_{j \in S^k(i;u)} s_{ij}}`, where
@@ -925,8 +1032,8 @@ def predict(self, i, j, Sk=None, MIN_VALUE=None, MAX_VALUE=None):
         # bui = µ + bu + bi
         #   The parameters bu and bi indicate the observed deviations of user
         #   u and item i, respectively, from the average
-        # 
-        # S^k(i; u): 
+        #
+        # S^k(i; u):
         #   Using the similarity measure, we identify the k items rated
         #   by u, which are most similar to i.
         #
@@ -946,7 +1053,7 @@ def predict(self, i, j, Sk=None, MIN_VALUE=None, MAX_VALUE=None):
         bui = bu + bi
         #if self._Mu: #TODO uncomment?
         #   bui += self._Mu
- 
+
         sim_ratings = []
         sum_similarity = 0.0
         for similar, sij in similars[1:]:
@@ -965,10 +1072,9 @@ def predict(self, i, j, Sk=None, MIN_VALUE=None, MAX_VALUE=None):
         Sumj_Sk = sum(sim_ratings)/sum_similarity
         rui = bui + Sumj_Sk
         predicted_value = rui
-        
+
         if MIN_VALUE:
             predicted_value = max(predicted_value, MIN_VALUE)
         if MAX_VALUE:
             predicted_value = min(predicted_value, MAX_VALUE)
         return float(predicted_value)
-
diff --git a/recsys/algorithm/factorize.pyc b/recsys/algorithm/factorize.pyc
index 79f7199fc03ca842f12de55634fa87558ea13eb6..cc7a947dcb9f76cedf3a58fec1a2f623f818422f 100644
GIT binary patch
delta 9576
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
NHGiP^_mSp1{vS;i@lyZ*

delta 7122
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
Mch$S<g|?Ib0|i`J`Tzg`

diff --git a/recsys/algorithm/matrix.py b/recsys/algorithm/matrix.py
index b4fc51e..36e0313 100644
--- a/recsys/algorithm/matrix.py
+++ b/recsys/algorithm/matrix.py
@@ -88,16 +88,11 @@ def get_cols(self): #can use to get rated items and remove from recommendation
     def get_additional_elements(self):  # can use to get rated items and remove from recommendation
         return self._additional_elements
 
-    # def create_blank(self,element_id,row_labels=None, col_labels=None):
-    #     if col_labels:
-    #
-    #     self._matrix = divisiSparseMatrix.from_named_lists(self._values, self._rows, self._cols,row_labels, col_labels)
-
-
 #row_labels specifies the row labels the complete matrix should have incase the inputted file doesn't include all indicies and it was saved in previous matrix (for update)
 #same explination for col_labels but for columns
 #matrix should have, in case it is larger than the largest index.
-    def create(self, data,row_labels=None, col_labels=None, foldin=False,truncate=False): #is_row is what I'm originally folding in
+    def create(self, data,row_labels=None, col_labels=None, foldin=False,truncate=False):
+         #is_row is what I'm originally folding in
         self._values = map(itemgetter(0), data)
         self._rows = map(itemgetter(1), data)
         self._cols = map(itemgetter(2), data)
@@ -154,4 +149,3 @@ def empty(self):
         if self._matrix:
             nrows, ncols = self._matrix.shape
         return not self._matrix or not (nrows and ncols)
-
diff --git a/recsys/datamodel/data.py b/recsys/datamodel/data.py
index f2f3d94..de4a5e8 100644
--- a/recsys/datamodel/data.py
+++ b/recsys/datamodel/data.py
@@ -17,6 +17,7 @@ def __init__(self):
         #:type data: list
         #"""
         self._data = list([])
+        self._tupleDict = {}
 
     def __repr__(self):
         s = '%d rows.' % len(self.get())
@@ -52,6 +53,13 @@ def get(self):
         :returns: a list of tuples
         """
         return self._data
+    def get_tuple_dict(self):
+        """
+        :returns: a dictionary of users or items and corresponding ratings
+        """
+        if not self._tupleDict:
+            raise ValueError('Tuple dictionary hasn\'t been created yet, please run split_train_test_foldin first then try again')
+        return self._tupleDict
 
     def add_tuple(self, tuple):
         """
@@ -94,6 +102,166 @@ def split_train_test(self, percent=80, shuffle_data=True):
 
         return train, test
 
+    def split_train_test_foldin(self,base=60,percentage_base_user=80, shuffle_data=True,is_row=True,force=True,data_report_path=None,id=None,ignore_rating_count=0):
+        """
+        Splits the data in three datasets: train, test, and foldin
+
+        :param base: % of training set to be used (Foldin set size = 100-base) for base SVD model (not folded)
+        :type base: int
+        :param percentage_base_user: % of user ratings per user (or item ratings per item depending on which is row and column) to be used as base for training or foldin (testing will be percentage of ratings from 100-percentage_base_user per user or item )
+        :type percentage_base_user: int
+
+        :param shuffle_data: shuffle dataset?
+        :type shuffle_data: Boolean
+
+        :param is_row: are you trying to foldin a row or a column ? yes-> row , no-> column
+        :type is_row: Boolean
+        :param force: clear the values in data
+        :type force: Boolean
+
+
+        The following parameters are used for when generating a report of the dataset distribution:
+        :param data_report_path: path to create report in
+        :type data_report_path: String
+        :param id: id number to be given to the report
+        :type id: String
+        :param ignore_rating_count: shuffle dataset?
+        :type ignore_rating_count: Boolean
+
+        :returns: a tuple <Data, Data, Data> for train, test, foldin
+        """
+        if force:
+            self._construct_dictionary(is_row=is_row,force=True)
+        elif len(self._tupleDict)==0:
+            self._construct_dictionary(is_row=is_row)
+        self._remove_ratings_count_from_dictionary(ignore_rating_count)
+        dictKeys=self._tupleDict.keys() #users
+        numberOfKeys= len(dictKeys) #number of users
+
+        train_list =[]
+        test_list=[]
+        foldin_list=[]
+
+        if shuffle_data:
+            shuffle(dictKeys)
+        train_list_keys=dictKeys[:int(round(numberOfKeys*base/100.0))]
+        if base==100:
+            foldin_list_keys=[]
+        else:
+            foldin_list_keys=dictKeys[-int(round(numberOfKeys*(100-base)/100.0)):]
+
+        for key in train_list_keys:
+            tupleList=self._tupleDict[key]
+            lengthTupleList=len(tupleList)
+            if shuffle_data:
+                shuffle(tupleList)
+
+            train_list.extend(tupleList[:int(round(lengthTupleList*percentage_base_user/100.0))])
+            if int(round(lengthTupleList*(100-percentage_base_user)/100.0)) !=0: #if test=0 then can't take that percentage so skip taking it's tuple for test
+                test_list.extend(tupleList[-int(round(lengthTupleList*(100-percentage_base_user)/100.0)):])
+
+        for key in foldin_list_keys:
+            tupleList=self._tupleDict[key]
+            lengthTupleList=len(tupleList)
+            if shuffle_data:
+                shuffle(tupleList)
+
+            foldin_list.extend(tupleList[:int(round(lengthTupleList*percentage_base_user/100.0))])
+            if int(round(lengthTupleList*(100-percentage_base_user)/100.0)) !=0: #if test=0 then can't take that percentage so skip taking it's tuple for test
+                test_list.extend(tupleList[-int(round(lengthTupleList*(100-percentage_base_user)/100.0)):])
+
+
+
+        length = len(self._data)
+        if VERBOSE:
+            print "total number of tuples:",length
+            print "percentage of data for training:",round((len(train_list)*1.0/length)*100),"%","with",len(train_list),"tuples"
+            print "percentage of data for testing:",round((len(test_list)*1.0/length)*100),"%","with",len(test_list),"tuples"
+            print "percentage of data for foldin:",round((len(foldin_list)*1.0/length)*100),"%","with",len(foldin_list),"tuples"
+            print "_____________"
+            print "percentage of users for foldin:",round((len(foldin_list_keys)*1.0/numberOfKeys*1.0)*100),"%","with",len(foldin_list_keys),"users"
+            print "percentage of users for training:",round((len(train_list_keys)*1.0/numberOfKeys*1.0)*100),"%","with",len(train_list_keys),"users"
+
+        if data_report_path:
+            myFile = open(data_report_path+"/data_distribution_report.txt", 'a+')
+
+            myFile.write("DataID:"+ str(id))
+            myFile.write("total number of tuples:"+ str(length))
+            myFile.write("\n")
+            myFile.write( "percentage of data for training:"+ str(round((len(train_list) * 1.0 / length) * 100))+ "%"+ "with"+str(len(train_list))+"tuples")
+            myFile.write("\n")
+            myFile.write( "percentage of data for testing:"+ str(round((len(test_list) * 1.0 / length) * 100))+ "%"+ "with"+ str(len(test_list))+ "tuples")
+            myFile.write("\n")
+            myFile.write("percentage of data for foldin:"+ str(round((len(foldin_list) * 1.0 / length) * 100))+ "%"+ "with"+ str(len(foldin_list))+ "tuples")
+            myFile.write("\n")
+            myFile.write("_____________")
+            myFile.write("\n")
+            myFile.write("percentage of users for foldin:"+ str(round((len(foldin_list_keys) * 1.0 / numberOfKeys * 1.0) * 100))+ "%"+ "with"+ str(len(foldin_list_keys))+ "users")
+            myFile.write("\n")
+            myFile.write("percentage of users for training:"+ str(round((len(train_list_keys) * 1.0 / numberOfKeys * 1.0) * 100))+ "%"+"with"+ str(len(train_list_keys))+ "users")
+            myFile.write("\n")
+            myFile.write("________________________________________________________________")
+            myFile.write("\n")
+
+            myFile.close()
+
+
+        train = Data()
+        train.set(train_list)
+        test = Data()
+        test.set(test_list)
+        foldin=Data()
+        foldin.set(foldin_list)
+
+        return train, test, foldin
+
+    def _remove_ratings_count_from_dictionary(self,count_threshold_to_remove):
+        '''
+        :param count_threshold_to_remove: The threshold number of ratings to be removed from the data.
+        :type count_threshold_to_remove: int
+        :return: void, it changes the data itself in the class.
+        '''
+        if count_threshold_to_remove==0:
+            return
+        removed=0
+        dictKeys=self._tupleDict.keys()
+        for key in dictKeys:
+            if len(self._tupleDict[key])<=count_threshold_to_remove:
+                del self._tupleDict[key]
+                removed+=1
+
+        print "users removed less than or equal threshold count=",removed,"users"
+        return
+
+    def _construct_dictionary(self, is_row=True,force=True):
+        '''
+
+        :param data: Data()
+        :param is_row: Boolean
+        :return: constructs a dictionary with the row or col as the keys (depending on which is being added) with values as the tuples
+        in self._batchDict
+        '''
+        # self._values = map(itemgetter(0), data)
+        # self._rows = map(itemgetter(1), data)
+        # self._cols = map(itemgetter(2), data)
+        key_idx = 1  # key index default is the row
+        if not is_row:
+            key_idx = 2
+        if force:    #construct new dictionary
+            self._tupleDict={}
+        # collecting the significant col or row tuples at one place to fold them in at once
+
+        for item in self._data:  # data is a list of tuples so item is 1 tuple
+            try:
+                self._tupleDict[item[key_idx]].append(item)
+            except KeyError:
+                self._tupleDict[item[key_idx]] = []
+                self._tupleDict[item[key_idx]].append(item)
+
+        # batch loaded , now need to fold them in one by one
+        if VERBOSE:
+            print "Dictionary created successfully"
+
     def load(self, path, force=True, sep='\t', format=None, pickle=False):
         """
         Loads data from a file
@@ -104,10 +272,10 @@ def load(self, path, force=True, sep='\t', format=None, pickle=False):
         :type force: Boolean
         :param sep: Separator among the fields of the file content
         :type sep: string
-        :param format: Format of the file content. 
+        :param format: Format of the file content.
             Default format is 'value': 0 (first field), then 'row': 1, and 'col': 2.
-            E.g: format={'row':0, 'col':1, 'value':2}. The row is in position 0, 
-            then there is the column value, and finally the rating. 
+            E.g: format={'row':0, 'col':1, 'value':2}. The row is in position 0,
+            then there is the column value, and finally the rating.
             So, it resembles to a matrix in plain format
         :type format: dict()
         :param pickle: is input file in  pickle format?
@@ -120,7 +288,7 @@ def load(self, path, force=True, sep='\t', format=None, pickle=False):
         if pickle:
             self._load_pickle(path)
         else:
-            i = 0 
+            i = 0
             for line in codecs.open(path, 'r', 'ISO-8859-1'): #was utf8 changed it to 'ISO-8859-1'
                 data = line.strip('\r\n').split(sep)
                 value = None
@@ -140,7 +308,7 @@ def load(self, path, force=True, sep='\t', format=None, pickle=False):
                             value = data[format['value']]
                         except KeyError, ValueError:
                             value = 1
-                        try: 
+                        try:
                             row_id = data[format['row']]
                         except KeyError:
                             row_id = data[1]
diff --git a/recsys/datamodel/data.pyc b/recsys/datamodel/data.pyc
index 176bf003ceacb312e03caca2d384a9ec88644cf4..cae0024d2cec36ea7ac9f0508473dc791dccbe96 100644
GIT binary patch
delta 5076
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

delta 1097
zcmZ8g&1(}u6o0cxHraI3q=}(OYg@4<Ta@&}4=NOmtrcn^lxYPiNL;d06Psq!&88Z#
zC>lI@DRZ-W5TqAHkX(B8Ry~MN75@P}TET;$7w64JkskKPoA=)Q=J!5!n>{@r^8JW}
zx8D4`vJv_a4gkQU_-dOQU$0Iv9jq`k6<CY_+8toUfQ7))q3MUkC|D6_0xWvYPG}NX
z>;fw)opGS^Mi<{9^QRJm(FM2;aG$_!fII;UVBR3YpEDGU`UR9rHP=N2dCPQ7UR3U?
z7=r-+t~@6yf9zW$NuKae#zMkNH35=>syrJi@EyNSdiY2G+(8*eB1xOGD@;Bj^3Dbt
zq>pb0W;*uYw$%tZ!q?SLQT6XdR6!HSt+_totT8vm-vw8Nlx)a~BEuNErAU8kPn%On
zh-dY5Pp5EGDFDgLYL!(i1J6RjJ$;jmxB4REN*Z-a=2FEg+_CEwi{e`}DZr40sC+&8
zo?PO&SZ`}@VAV%Hx6HvagbeUUN4ErKQy0iUtC<QCQU1g9VbafErgy`#lQ*4`%lHE$
zrO8&<LI{6lB=4X=7YkTuRN1uS*p7>D&9a7f2#Q0|tmT(kgZ5)?SinrhVz*?R_Ms08
zS}NP7Yea-a|MjhEx<xN&I|bHmUDd_-lI^URt~-QwwNzLxGa40Ud;Aqf4<J(yLNCH0
zZVs&yv$Z=kO9r#(!R@3c5KbbXhW2prIDwN<aVoE&_2Sryk{U+$2*RiUR~L}4nQJWH
ze%_<v=Q7EeZp^|$rJNhKm<rMt%65&lM}Zj=2w4HLGP~~nuNPw-avgtXH03RDb@*3i
zN_cYyuDEv(Evvp#mFi_3`%8#Vi<EET^Oh@^GZOsS$nncK7m77d+42}#aRE~3MYCLE
za&hFp;SCAjT5#%&8kj2=Q??L$L@yys^UPS0KN$_5#+Iu-m1sdt)qGk&^Xa;<;?eHp
XHBHk8kpc|t5S49}z02}1qq~0q$q3K)


From 9ab96c699cf99f46d4c6556d9357fa6504eb9812 Mon Sep 17 00:00:00 2001
From: Ibrahim Abou Elseoud <Ibrahim.elseoud@gmail.com>
Date: Thu, 8 Jun 2017 16:29:06 +0200
Subject: [PATCH 4/7] updating comments and documentation

---
 recsys/algorithm/matrix.py | 4 ++--
 recsys/datamodel/data.py   | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/recsys/algorithm/matrix.py b/recsys/algorithm/matrix.py
index 36e0313..253a07f 100644
--- a/recsys/algorithm/matrix.py
+++ b/recsys/algorithm/matrix.py
@@ -85,7 +85,7 @@ def get_rows(self): #can use to get rated items and remove from recommendation
     def get_cols(self): #can use to get rated items and remove from recommendation
         return self._cols
 
-    def get_additional_elements(self):  # can use to get rated items and remove from recommendation
+    def get_additional_elements(self):  # can use to get additional items to either fold or truncate
         return self._additional_elements
 
 #row_labels specifies the row labels the complete matrix should have incase the inputted file doesn't include all indicies and it was saved in previous matrix (for update)
@@ -131,7 +131,7 @@ def update(self, matrix,is_batch=False): #isbatch is for creating the final spar
     def squish(self,squishFactor): #remove additional empty fields created by divisiSparseMatrix
         self._matrix=self._matrix.squish(squishFactor)
 
-    def index_sparseMatrix(self):
+    def index_sparseMatrix(self): #create the divisi2 sparse matrix from already existing values
         self._matrix = divisiSparseMatrix.from_named_lists(self._values, self._rows, self._cols)
 
     def empty(self):
diff --git a/recsys/datamodel/data.py b/recsys/datamodel/data.py
index de4a5e8..6919229 100644
--- a/recsys/datamodel/data.py
+++ b/recsys/datamodel/data.py
@@ -125,8 +125,8 @@ def split_train_test_foldin(self,base=60,percentage_base_user=80, shuffle_data=T
         :type data_report_path: String
         :param id: id number to be given to the report
         :type id: String
-        :param ignore_rating_count: shuffle dataset?
-        :type ignore_rating_count: Boolean
+        :param ignore_rating_count: The threshold number of ratings to be removed from the data.
+        :type ignore_rating_count: int
 
         :returns: a tuple <Data, Data, Data> for train, test, foldin
         """

From af7c0a210dcc9414dddae2bd53dc45e63b03ca24 Mon Sep 17 00:00:00 2001
From: Ibrahim Abou Elseoud <Ibrahim.elseoud@gmail.com>
Date: Mon, 14 Aug 2017 15:42:15 +0200
Subject: [PATCH 5/7] Updated the README.rst to include the incremental svd
 update

---
 README.rst                       | 160 ++++++++++++++++++++++++++++---
 recsys/__init__.pyc              | Bin 556 -> 0 bytes
 recsys/algorithm/__init__.pyc    | Bin 251 -> 0 bytes
 recsys/algorithm/baseclass.pyc   | Bin 11617 -> 0 bytes
 recsys/algorithm/factorize.pyc   | Bin 32828 -> 0 bytes
 recsys/algorithm/matrix.pyc      | Bin 8411 -> 0 bytes
 recsys/datamodel/__init__.pyc    | Bin 226 -> 0 bytes
 recsys/datamodel/data.pyc        | Bin 12984 -> 0 bytes
 recsys/evaluation/__init__.pyc   | Bin 269 -> 0 bytes
 recsys/evaluation/baseclass.pyc  | Bin 4781 -> 0 bytes
 recsys/evaluation/prediction.pyc | Bin 3802 -> 0 bytes
 11 files changed, 147 insertions(+), 13 deletions(-)
 delete mode 100644 recsys/__init__.pyc
 delete mode 100644 recsys/algorithm/__init__.pyc
 delete mode 100644 recsys/algorithm/baseclass.pyc
 delete mode 100644 recsys/algorithm/factorize.pyc
 delete mode 100644 recsys/algorithm/matrix.pyc
 delete mode 100644 recsys/datamodel/__init__.pyc
 delete mode 100644 recsys/datamodel/data.pyc
 delete mode 100644 recsys/evaluation/__init__.pyc
 delete mode 100644 recsys/evaluation/baseclass.pyc
 delete mode 100644 recsys/evaluation/prediction.pyc

diff --git a/README.rst b/README.rst
index 27e9d66..1444df5 100644
--- a/README.rst
+++ b/README.rst
@@ -4,6 +4,19 @@ python-recsys
 
 A python library for implementing a recommender system.
 
+- Now supports incrementally adding new users or items instead of building the model from scratch for these new users or items via the folding-in technique which was mentioned in Sarwar et al.'s `paper`_ (Titled: Incremental Singular Value Decomposition Algorithms for Highly Scalable Recommender Systems), this latest commit is simply an implementation to it for python-recsys.
+
+.. _`paper`: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.3.7894&rep=rep1&type=pdf
+
+- A `Demonstration video is available`_  for this latest commit in form of a demo site built using the MEAN stack which uses the updated python-recsys as backend for the recommender which folds-in the website's user in to the SVD model and gets recommendations instantaneously instead of building the model from scratch.
+
+.. _`Demonstration video is available`:  https://youtu.be/tIvQxBfa2d4
+
+-There is also an accompanying `bachelor thesis paper`_ (For those interested) which outlines the background, architecture and discusses the "Folding-in" approach.
+
+.. _`bachelor thesis paper`: https://drive.google.com/file/d/0BylQe2cRVWE_RmZoUTJYSGZNaXM/view
+
+
 Installation
 ============
 
@@ -57,8 +70,8 @@ Example
 
     from recsys.algorithm.factorize import SVD
     svd = SVD()
-    svd.load_data(filename='./data/movielens/ratings.dat', 
-                sep='::', 
+    svd.load_data(filename='./data/movielens/ratings.dat',
+                sep='::',
                 format={'col':0, 'row':1, 'value':2, 'ids': int})
 
 2. Compute Singular Value Decomposition (SVD), M=U Sigma V^t:
@@ -66,11 +79,11 @@ Example
 ::
 
     k = 100
-    svd.compute(k=k, 
-                min_values=10, 
-                pre_normalize=None, 
-                mean_center=True, 
-                post_normalize=True, 
+    svd.compute(k=k,
+                min_values=10,
+                pre_normalize=None,
+                mean_center=True,
+                post_normalize=True,
                 savefile='/tmp/movielens')
 
 3. Get similarity between two movies:
@@ -111,10 +124,10 @@ Example
     USERID = 1
 
     svd.predict(ITEMID, USERID, MIN_RATING, MAX_RATING)
-    # Predicted value 5.0 
+    # Predicted value 5.0
 
     svd.get_matrix().value(ITEMID, USERID)
-    # Real value 5.0 
+    # Real value 5.0
 
 6. Recommend (non-rated) movies to a user:
 
@@ -152,7 +165,130 @@ Example
      (4801, 5.4947999354188548),
      (1131, 5.4941438045650068),
      (2339, 5.4916048051511659)]
-    
+
+
+Example for incremental update
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+1. Load Movielens dataset and prepare for training and testing:
+
+::
+
+    import recsys.algorithm
+    recsys.algorithm.VERBOSE = True
+
+    from recsys.algorithm.factorize import SVD
+    from recsys.datamodel.data import Data
+
+    filename = “(your movielens file path here)”
+
+    #In movielens dataset, the user is at 0 so I made them the row (could keep it as above {'col':0, 'row':1, 'value':2, 'ids': int} but I changed order to emphasis a parameter in an upcoming function)
+    format = {'col':1, 'row':0, 'value':2, 'ids': int}
+
+    data = Data()
+    data.load(filename, sep='::', format=format)
+    #splits the dataset according to row or column (based on is_row=true or false) which causes there to be no overlap (of users for example) between train and foldin dataset
+    train, test, foldin = data.split_train_test_foldin(base=60,percentage_base_user=80,shuffle_data=True,is_row=True) #since users are in the row so is_row=true
+
+    # Returns: a tuple <Data, Data, Data> for train, test, foldin
+    # Prints: (If VERBOSE=True)
+    total number of tuples: 1000209
+    percentage of data for training: 48.0 % with 479594 tuples
+    percentage of data for testing: 20.0 % with 200016 tuples # 100-percentage_base_user per user (percentage of tuples which means the ratings since a user has many tuples(ratings))
+    percentage of data for foldin: 32.0 % with 320599 tuples
+    _____________
+    percentage of users for foldin: 40.0 % with 2416 users # 100-base= foldin (percentage of users)
+    percentage of users for training: 60.0 % with 3624 users #base for training (percentage of users)
+
+2. Compute Singular Value Decomposition (SVD), M=U Sigma V^t:
+
+::
+
+    svd = SVD()
+    svd.set_data(train)
+    svd.compute(k=100,
+            	min_values=1,
+            	pre_normalize=None,
+            	mean_center=False,
+            	post_normalize=True)
+
+    # Prints:
+    Creating matrix (479594 tuples)
+    Matrix density is: 3.7007%
+    Updating matrix: squish to at least 1 values
+    Computing svd k=14, min_values=1, pre_normalize=None, mean_center=False, post_normalize=False
+
+3. "Foldin" those new users or items (update model instead of updating from scratch)
+
+::
+
+    svd.load_updateDataBatch_foldin(data=foldin,is_row=True)
+
+    # Prints: (If VERBOSE=True)
+    before updating, M= (3624, 3576)
+    done updating, M= (6040, 3576) # Folds in all the new users (not previously in model)
+
+4. Recommend (non-rated) movies to a NEW user
+::
+
+    user_id=foldin[0][1] #returns userID which is in foldin dataset BUT not in train dataset
+    svd.recommend(user_id,is_row=True,only_unknowns=True) #The userID is in row and gets only the unrated (unknowns)
+
+    # Returns: <ITEMID, Predicted Rating>
+    [(1307, 3.6290483094468913),
+    (1394, 3.5741565545425957),
+    (1259, 3.5303836262378048),
+    (1968, 3.4565426585553927),
+    (2791, 3.3470277643217203),
+    (1079, 3.268283171487782),
+    (1198, 3.2381080336246675),
+    (593, 3.204915630088236),
+    (1270, 3.1859618303393233),
+    (2918, 3.1548530640630252)]
+
+5. Recommend (non-rated) movies to a NEW user and validate not in base model (prior to folding-in)
+::
+
+    # BEFORE running points 3 and 4 (prior to calling svd.load_updateDataBatch_foldin)
+
+    user_id=foldin[0][1] #returns userID which is in foldin dataset BUT not in train dataset
+
+    # Try block to validate that the userID is new and not in the base model
+    try:
+        print "Getting recommendation for user_id which was not in original model training set"
+        print "recommendations:",svd.recommend(user_id)
+    except Exception:
+        print "New user not in base model so in except block and will foldin the foldin dataset (update the model NOT calculate from scratch)"
+        svd.load_updateDataBatch_foldin(data=foldin,format=format,is_row=True,truncate=True,post_normalize=True)
+        print "recommendations:",svd.recommend(user_id,is_row=True,only_unknowns=True) #The userID is in row and get us only the unrated (unknowns)
+
+
+    # Prints:
+    Getting recommendation for user_id which was not in original model training set
+    recommendations: New user not in base model so in except block and will foldin the foldin dataset (update the model NOT calculate from scratch)
+    before updating, M= (3624, 3576)
+    done updating, M= (6040, 3576)
+    recommendations: [(1307, 3.6290483094468913), (1394, 3.5741565545425957), (1259, 3.5303836262378048), (1968, 3.4565426585553927), (2791, 3.3470277643217203), (1079, 3.268283171487782), (1198, 3.2381080336246675), (593, 3.204915630088236), (1270, 3.1859618303393233), (2918, 3.1548530640630252)]
+
+
+6. Load previous SVD model and foldin NEW users from file then instantly get recommendations
+::
+
+    format = {'col':1, 'row':0, 'value':2, 'ids': int}
+
+    svd = SVD()
+    #load base svd model
+    svd.load_model('SVDModel')
+
+    # load new users by their movie rating data file and use it to fold-in the users into the model (loads data and folds in)
+    svd.load_updateDataBatch_foldin(filename = 'newUsers.dat', sep='::', format=formate, is_row=True)
+
+    # gets recommendedations
+    print "recommendations:", svd.recommend(new_userID,is_row=True,only_unknowns=True)
+
+
+- All the normal functionalities of python-recsys are compatible with the incremental update commit. The incremental update can even work if you load the model then foldin a new user or users or even items.
+
+- Please note that preexisting users can't be folded-in only new users which aren't already in the svd model.
 
 Documentation
 ~~~~~~~~~~~~~
@@ -168,10 +304,8 @@ To create the HTML documentation files from doc/source do:
     cd doc
     make html
 
-HTML files are created here: 
+HTML files are created here:
 
 ::
 
     doc/build/html/index.html
-
-
diff --git a/recsys/__init__.pyc b/recsys/__init__.pyc
deleted file mode 100644
index a6bc57bd3ac78b000f3d61b363cfbd46fbc14d5c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 556
zcmY*VO;6k~5S?W|SeCZxPsnjEY@o-gswkD{0R$^?LAhAQ9&KFNF|sE}`*ZmX`~jX5
z1R{xN-kb58%*^!V>ne25_lDk{8T>g;zJ#$v4eW%P3E2Z`4#-ZanUbARGaDjvvU6${
zWEa#NlEt?qdqj3cJKO<2rn~hQl#&HZVw3kdp8-DZrH$Rm82MTKsj>@?o~tolAs$2E
z&T^_C5f1qns@rUIs{XPMI#0n*H$SBKf7|Q)9X^_pI^PBD{00@IjB~-F<p@CgT})2e
zFfgk{hiKW$3ORn#zSrVnCv(tVoX<DwqLL+yQk@R0lpGGkMgv@QO6eYxB+$~JQcs*R
zs=RaW40pLmzYTe`)(H+5NvW?Cmv+I%dKumJ8Fc)W_DHR9k3LMk0<GH^xONXow=UG%
zy=ip{+Z6w^kz8MB)3T4L1nN)AuE(9%O1aL7QX6E;h`+Fm-t+GX@UYCq<n#Lq)B%nB

diff --git a/recsys/algorithm/__init__.pyc b/recsys/algorithm/__init__.pyc
deleted file mode 100644
index 89220e34927d884b6c3dce3b3a58b9984843c127..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 251
zcmYLEO9}!p49(2o7u<RVH`)t`$bc?Y6h+*W+Nsk@JC!zq<83{I7chRnKwe(n3n9s#
zy{@YsdkMctsZJ?lpFjZ|5-0$Q1j-Z+DHJJe1{qn{Bya+QxnYOsNl5g;+|}Z1H%OZc
z8x^*Q?PlwIxtV=9=fYaf2PDHE;+9&by;IB_gV4sYRV}p$ZWa7lNsKHPQY-7jTVR(O
pwXcRj$yi7BE5ep^18T=OH<dxoM>N-_&12__t5uVPczplZOkOKXJ&^za

diff --git a/recsys/algorithm/baseclass.pyc b/recsys/algorithm/baseclass.pyc
deleted file mode 100644
index 50b4b32a88900c7b87a8c0da46433b026d69f6b3..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 11617
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

diff --git a/recsys/algorithm/factorize.pyc b/recsys/algorithm/factorize.pyc
deleted file mode 100644
index cc7a947dcb9f76cedf3a58fec1a2f623f818422f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 32828
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

diff --git a/recsys/algorithm/matrix.pyc b/recsys/algorithm/matrix.pyc
deleted file mode 100644
index f4df053d8a20bc30468603f38761637e803476ba..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 8411
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

diff --git a/recsys/datamodel/__init__.pyc b/recsys/datamodel/__init__.pyc
deleted file mode 100644
index 8d3822860cf95760a3f628de56e2c4b7be24950d..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 226
zcmYLDK?(vf4D4D#5j^>cUbG(&5g#B6B8Znz+h8m0R@&}ipX(d^fa$VeNG6kENW#DC
zRp>r9&8Fbgk5qX>U?>}AX6(t?oj7L~F(<uLFsi7LxPoCMea3VaO*JeC0F?Itf++h#
zzS1ad3@F_rDeHo42W?dfZHh0{CCii27JW=rknf?`*liQ)JY+ewdOJjeUIMtz6@VT2
Qmb7_Ka_{j_zsOYV2d&&UHvj+t

diff --git a/recsys/datamodel/data.pyc b/recsys/datamodel/data.pyc
deleted file mode 100644
index cae0024d2cec36ea7ac9f0508473dc791dccbe96..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 12984
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
HjOYFrv)e*0

diff --git a/recsys/evaluation/__init__.pyc b/recsys/evaluation/__init__.pyc
deleted file mode 100644
index 0c3acb92feecc6b7ce41ead6f886938ec678ce78..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 269
zcmYL@U1~xx5QXP@s}`jTa0mCL%?3o;B7G_<{iFCY#3Yz$^CL+V?s8p2H_%BDIxyeN
znK=)q{kfU=aQ;+qsg~zGcb+jcz?Rv79)NCP-@=ynD|qdO{G}12Tvwk_lqrTXVR1-C
zlPXn?Stc32DePC>pt`4wm-YK<iBHe#hqpfmqI3?i;}`rRf3OrbdLw9;q$Mvly|Oa-
zO^RPel`I~mvc|<!58}`l8^difD)(7jhB--hlr@V$q(BAnj*T_*+(&(MX3_ATYqnAI
E3qL|aQUCw|

diff --git a/recsys/evaluation/baseclass.pyc b/recsys/evaluation/baseclass.pyc
deleted file mode 100644
index fa9dc9bb8e2b08d3939d210755c6ca6b9c77ad9b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 4781
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
Yw>;|_Pr-Y;;k<AYp~b<r<L&r=0va5U;Q#;t

diff --git a/recsys/evaluation/prediction.pyc b/recsys/evaluation/prediction.pyc
deleted file mode 100644
index 4631a7f19c45d8d8446fa8521b00acfb5fd92c82..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3802
zcmdUx>u=md5WvUwJ(5d818LKwmB3IT9a6}tq7qUSDiRX<g<PY((8oz&ZR}0X!H<h~
zui{YhrHK#{U-%Q@Z{RBu%*^^8E$z29P$xULFRy2IW`8^0p9_sN`{Qv4<>TS+mss@A
zC?foKAOT1n=sMs~+l9o1)Pt^P+a4qpNUPAT+I9t!8l-jT*0Eg$Rfj_dq8eNPoC7@R
z&OlTj_0EiYXCay$^)|-64T$ET)0{_D;=l1}I#@FmQtzZNwl#yTzLJH`v%>bwVUKw5
zZF@WT`kM!v`?j-zojXruGLR<DGec(XZtgs1_7?W(Pf#czYth)Q3z0`#aDnz$xQ<O1
zE?hEKbegQnFZfEs$7ff`%-=lFc``7{-zkc`Xz;+@zAR+wN7Bf<zVywYpC~^>HDsK{
z*^zI0%D-RagDmp*i-GC!%r#$Pn}+t4Hr!<wGsC_r*V;&8ZPt8}_joXLQ5h;^;8$4m
z4HO3d9XLe~xNz*ksRQTMn;qVqc9UkCDor2sRUybMLmL|esGtdgCM{_<UL}WIA!C}@
z3oP_wthIW1s#@_uA$xJ!+8c&FS)_YKeyl>HTU#>hsU$C`qtzdpUY>ngs8A1etNc-8
z|FrsrisF!5wca0!8ZLeXizYR|2}xbjmipPq-?4&L6}RBjC0QDbHoLHbwi=gA@1Wap
z6OJonep#<l{RbEH9+(=r8k^4?$k5z6?LoO|vlP4zy8g_AAKPFsGQH6fyc{mS{oNfn
ztCU|jt-$IT@A+{RxPkkQuVB|$tIu(<lV|YAYHQ|rf>n{#x`w)trdD|lwzj^5rk1f{
zI94c1#u1{G(SA=p#Q=#94^^Qu<Lme*r9V8v_xOoP@|j|fbJ+2DkOx|@pJ{s&d~*SR
zgCm|0@VEGdz+gDy1^_(7<D@vu1$o1UO&x1tp7!ww*!5+0WNU{>E=`kX(j?ppfC4ui
zWkCLBCG;TWf%vjwi{k1%EP5FQIP=bR_kHJzx8&5E51a-3TXEV=QnEY{nM_p>u>C=h
z=FuRbCK(h2Q66HQPER|}6o;*#^(VGm!eOD$&tTD{bgfo%<~>$P>~>xc?|#K~88#x%
zjfGz4WFU*T4|z6hiID6orko&G3fjPl*WNtc=-PRaZlnm)?Jukl-EK48ZkKdJER-lI
zf3B9e@GR>ri5Mj4f8t(k#JcYZZqKo92F&b;b&YA(Jp|+uv2JQPX5Ge!btT>`>uS@i
zdzpgFyx+#mbHU6r_NFu}u}9>yro3MA5!J*j2`nH8WQ;w*c?0Ddf~UMgHKr8@2%TfG
zNa9@-6O0Lhhgc$UmBcj~#c~<<BI&e-Wz4RRUtpJCvP%%$#B~xYBvwffA1)tHw3#1a
znZ^@E`ku|<Lag#bH_;XreG7$pRYr!n7!JK87b};;EUPF~!b#s-l#AK8yNQ<n*I?Qv
z7mzP7?V<~t3f(Xarq8fardp%i@*Jn&u^r>0<PIj%4y+<|C=FxIwrw6lpq;XoPZ=Lh
z`jD8MMpA*3Rp#?$9<clPWlbi7v-JNvP81_wkcN}nzTG5}@tvPa(__pb#Boj-_lN7+
zNTUU9uOX7z+<SeJd)pk4*8SJe`hnCcOr+Mr!*Ndd1k;hGPn^QFH+pGuGPYsUuX|*R
RrchaQ7F{gf@{Q%2{{X&ENcjK&


From e2c0ffedff725114805298c93ae8a511c286f801 Mon Sep 17 00:00:00 2001
From: Ibrahim Abou Elseoud <ibrahim.elseoud@gmail.com>
Date: Mon, 14 Aug 2017 15:46:43 +0200
Subject: [PATCH 6/7] Update README.rst

---
 README.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.rst b/README.rst
index 1444df5..a51c658 100644
--- a/README.rst
+++ b/README.rst
@@ -12,7 +12,7 @@ A python library for implementing a recommender system.
 
 .. _`Demonstration video is available`:  https://youtu.be/tIvQxBfa2d4
 
--There is also an accompanying `bachelor thesis paper`_ (For those interested) which outlines the background, architecture and discusses the "Folding-in" approach.
+- There is also an accompanying `bachelor thesis paper`_ (For those interested) which outlines the background, architecture and discusses the "Folding-in" approach.
 
 .. _`bachelor thesis paper`: https://drive.google.com/file/d/0BylQe2cRVWE_RmZoUTJYSGZNaXM/view
 

From 986058d9afd677069a9e52be3b2d270d4b146318 Mon Sep 17 00:00:00 2001
From: Ibrahim Abou Elseoud <ibrahim.elseoud@gmail.com>
Date: Mon, 14 Aug 2017 16:33:37 +0200
Subject: [PATCH 7/7] Update README.rst

---
 README.rst | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.rst b/README.rst
index a51c658..d09f9a5 100644
--- a/README.rst
+++ b/README.rst
@@ -4,7 +4,9 @@ python-recsys
 
 A python library for implementing a recommender system.
 
-- Now supports incrementally adding new users or items instead of building the model from scratch for these new users or items via the folding-in technique which was mentioned in Sarwar et al.'s `paper`_ (Titled: Incremental Singular Value Decomposition Algorithms for Highly Scalable Recommender Systems), this latest commit is simply an implementation to it for python-recsys.
+Incremental SVD update for python-recsys
+========================================
+- python-recsys now supports incrementally adding new users or items instead of building the model from scratch for these new users or items via the folding-in technique which was mentioned in Sarwar et al.'s `paper`_ (Titled: Incremental Singular Value Decomposition Algorithms for Highly Scalable Recommender Systems), this latest commit is simply an implementation to it for python-recsys.
 
 .. _`paper`: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.3.7894&rep=rep1&type=pdf
 
@@ -16,7 +18,6 @@ A python library for implementing a recommender system.
 
 .. _`bachelor thesis paper`: https://drive.google.com/file/d/0BylQe2cRVWE_RmZoUTJYSGZNaXM/view
 
-
 Installation
 ============
 
@@ -166,7 +167,6 @@ Example
      (1131, 5.4941438045650068),
      (2339, 5.4916048051511659)]
 
-
 Example for incremental update
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1. Load Movielens dataset and prepare for training and testing: