Skip to content

Commit

Permalink
Merge pull request #16 from JuliaRecsys/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
filipebraida committed Oct 14, 2019
2 parents d9a86da + 54e63b3 commit 1448322
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 25 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Expand Up @@ -5,3 +5,7 @@ docs/site/
*.jl.mem
*.jld
deps/deps.jl

Manifest.toml
docs/Manifest.toml
test/coverage/Manifest.toml
28 changes: 23 additions & 5 deletions .travis.yml
@@ -1,18 +1,36 @@
# Documentation: http://docs.travis-ci.com/user/languages/julia/
language: julia

os:
- linux

julia:
- 1.0
- 1.2
- nightly

matrix:
allow_failures:
- julia: nightly

codecov: true

coveralls: true

notifications:
email: false
# uncomment the following lines to override the default test script
#script:
# - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
# - julia -e 'Pkg.clone(pwd()); Pkg.build("Persa"); Pkg.test("Persa"; coverage=true)'
after_success:
- julia -e 'import Pkg; ps=Pkg.PackageSpec(name="Documenter", version="0.19"); Pkg.add(ps); Pkg.pin(ps)'
- julia -e 'import Pkg; cd(Pkg.dir("Persa")); include(joinpath("docs", "make.jl"))'
# push coverage results to Coveralls
- julia -e 'import Pkg; cd(Pkg.dir("Persa")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder()); Codecov.submit(process_folder())'

jobs:
include:
- stage: "Documentation"
julia: 1.0
os: linux
script:
- julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd()));
Pkg.instantiate()'
- julia --project=docs/ docs/make.jl
after_success: skip
18 changes: 18 additions & 0 deletions Project.toml
@@ -0,0 +1,18 @@
name = "Persa"
uuid = "86800b63-3017-5277-aca5-15d7d6a5f2b2"
keywords = ["recoomendersystems", "recsys", "collaborativefiltering"]
license = "MIT"
desc = " Recommender System framework for Julia."
version = "0.1.2"

[deps]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[extras]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Random"]
2 changes: 0 additions & 2 deletions REQUIRE

This file was deleted.

5 changes: 5 additions & 0 deletions docs/Project.toml
@@ -0,0 +1,5 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"

[compat]
Documenter = "~0.19"
73 changes: 67 additions & 6 deletions src/dataset.jl
Expand Up @@ -12,6 +12,8 @@ struct Dataset{T <: Number} <: AbstractDataset{T}
items::Int
end

sample(dataset::Dataset, index::Array) = Persa.Dataset(dataset[index], dataset.users, dataset.items, dataset.preference)

Base.string(x::Dataset) = string("""Collaborative Filtering Dataset
- # users: $(users(x))
- # items: $(items(x))
Expand Down Expand Up @@ -126,14 +128,56 @@ function Base.getindex(dataset::AbstractDataset{T}, i::Int)::UserPreference{T} w
end
end

function Base.getindex(dataset::AbstractDataset, user::Int, c::Colon)
elements = collect(dataset.ratings[user, :])
return [UserPreference(user, idx, elements[idx]) for idx in findall(!isnan, elements)]
function Base.getindex(dataset::AbstractDataset{T}, user::Int, c::Colon) where T
elements = Persa.UserPreference{T}[]

for i=1:items(dataset)
value = dataset[user, i]
if !isnan(value)
push!(elements, UserPreference(user, i, value))
end
end

return elements
end

function Base.getindex(dataset::AbstractDataset{T}, user::Int, index::UnitRange{Int}) where T
elements = Persa.UserPreference{T}[]

for i=1:length(index)
value = dataset[user, index[i]]
if !isnan(value)
push!(elements, UserPreference(user, index[i], value))
end
end

return elements
end

function Base.getindex(dataset::AbstractDataset{T}, c::Colon, item::Int) where T
elements = Persa.UserPreference{T}[]

for i=1:users(dataset)
value = dataset[i, item]
if !isnan(value)
push!(elements, UserPreference(i, item, value))
end
end

return elements
end

function Base.getindex(dataset::AbstractDataset, c::Colon, item::Int)
elements = collect(dataset.ratings[:, item])
return [UserPreference(idx, item, elements[idx]) for idx in findall(!isnan, elements)]
function Base.getindex(dataset::AbstractDataset{T}, index::UnitRange{Int}, item::Int) where T
elements = Persa.UserPreference{T}[]

for i=1:length(index)
value = dataset[index[i], item]
if !isnan(value)
push!(elements, UserPreference(index[i], item, value))
end
end

return elements
end

function Base.getindex(dataset::AbstractDataset{T}, index::Vector{Int}) where T
Expand All @@ -158,10 +202,27 @@ function Base.getindex(dataset::AbstractDataset{T}, index::UnitRange{Int}) where
return elements
end

function Base.getindex(dataset::AbstractDataset{T}, users::UnitRange{Int}, items::UnitRange{Int}) where T
elements = UserPreference{T}[]

for user in users, item in items
value = dataset[user, item]
if !isnan(value)
push!(elements, UserPreference(user, item, value))
end
end

return elements
end

Base.getindex(dataset::AbstractDataset, c1::Colon, c2::Colon) = dataset[1:end, 1:end]

Base.getindex(dataset::AbstractDataset, c::Colon) = dataset[1:length(dataset)]

Base.iterate(dataset::AbstractDataset, state = 1) = state > length(dataset) ? nothing : (dataset[state], state+1)

Base.lastindex(dataset::AbstractDataset, d::Int) = d == 1 ? users(dataset) : items(dataset)

function Statistics.mean(dataset::AbstractDataset)
μ = 0
total = 0
Expand Down
24 changes: 14 additions & 10 deletions src/learn.jl
@@ -1,4 +1,4 @@
abstract type Model
abstract type Model{T} <: AbstractDataset{T}
end

users(model::Model) = model.users
Expand All @@ -7,25 +7,29 @@ items(model::Model) = model.items
checkuser(model::Model, user::Int) = (user >= 0 && user <= users(model)) ? user : error("invalid user id ($user)")
checkitem(model::Model, item::Int) = (item >= 0 && item <= items(model)) ? item : error("invalid item id ($item)")

predict(model::Model, user::Int, item::Int) = 0
predict(model::Model, user::Int, item::Int) where T = missing

train!(model::Model, dataset::Dataset) = nothing

function Base.getindex(model::Model, user::Int, item::Int)
function Base.getindex(model::Model{T}, user::Int, item::Int) where T
checkuser(model, user)
checkitem(model, item)

PredictRating(correct(predict(model, user, item), model.preference), model.preference)
end
value = predict(model, user, item)

Base.getindex(model::Model, user::Int, c::Colon) = [model[user, item] for item = 1:items(model)]
Base.getindex(model::Model, c::Colon, item::Int) = [model[user, item] for user = 1:users(model)]
if !ismissing(value)
return PredictRating(correct(value, model.preference), model.preference)
end

return MissingRating{T}()
end

function Base.getindex(model::Model, dataset::Dataset)
predicts = Vector{Float64}(undef, length(dataset))
function Base.getindex(model::Model{T}, dataset::Dataset{T}) where T
predicts = Vector{UserPreference{T}}(undef, length(dataset))

for i = 1:length(dataset)
(u, v, r) = dataset[i]
predicts[i] = model[u, v]
predicts[i] = UserPreference{T}(u, v, model[u, v])
end

return predicts
Expand Down
20 changes: 18 additions & 2 deletions src/rating.jl
Expand Up @@ -52,6 +52,9 @@ Base.show(io::IO, x::MissingRating) = print(io, "Rating: ", x)
Base.isnan(rating::AbstractRating{T}) where T <: Number = false
Base.isnan(rating::MissingRating{T}) where T <: Number = true

Base.ismissing(rating::MissingRating{T}) where T <: Number = true
Base.ismissing(rating::AbstractRating{T}) where T <: Number = false

Base.zero(::Type{AbstractRating{T}}) where T <: Number = MissingRating{T}()

function convert(values::Array{T}, preference::Preference{T})::Array{AbstractRating{T}} where T <: Number
Expand Down Expand Up @@ -84,5 +87,18 @@ Base.:*(x::Number, r1::AbstractRating{T}) where {T <: Number} = value(r1) * x
Base.:/(r1::AbstractRating{T}, x::Number) where {T <: Number} = value(r1) / x
Base.:/(x::Number, r1::AbstractRating{T}) where {T <: Number} = value(r1) / x

Base.isequal(r1::AbstractRating{T}, x::Number) where {T <: Number} = value(r1) == x
Base.:!=(r1::AbstractRating{T}, x::Number) where {T <: Number} = value(r1) == x
Base.:(==)(r1::AbstractRating{T}, x::Number) where {T <: Number} = value(r1) == x
Base.:(!=)(r1::AbstractRating{T}, x::Number) where {T <: Number} = value(r1) != x

Base.:(==)(r1::AbstractRating{T}, r2::AbstractRating{T}) where {T <: Number} = value(r1) == value(r2)
Base.:(!=)(r1::AbstractRating{T}, r2::AbstractRating{T}) where {T <: Number} = value(r1) != value(r2)

Base.:(>=)(r1::AbstractRating{T}, x::Number) where {T <: Number} = value(r1) >= x
Base.:(<=)(r1::AbstractRating{T}, x::Number) where {T <: Number} = value(r1) <= x
Base.:(>)(r1::AbstractRating{T}, x::Number) where {T <: Number} = value(r1) > x
Base.:(<)(r1::AbstractRating{T}, x::Number) where {T <: Number} = value(r1) < x

Base.:(>=)(r1::AbstractRating{T}, r2::AbstractRating{T}) where {T <: Number} = value(r1) >= value(r2)
Base.:(<=)(r1::AbstractRating{T}, r2::AbstractRating{T}) where {T <: Number} = value(r1) <= value(r2)
Base.:(>)(r1::AbstractRating{T}, r2::AbstractRating{T}) where {T <: Number} = value(r1) > value(r2)
Base.:(<)(r1::AbstractRating{T}, r2::AbstractRating{T}) where {T <: Number} = value(r1) < value(r2)
61 changes: 61 additions & 0 deletions test/datasetTest.jl
Expand Up @@ -77,6 +77,67 @@
end
end

@testset "Column Index Tests" begin
for user=1:Persa.users(dataset1)
@test length(dataset1[user, :]) == length(dataset1[user, 1:end])
end

for item=1:Persa.items(dataset1)
@test length(dataset1[:, item]) == length(dataset1[1:end, item])
end

for user=1:Persa.users(dataset2)
@test length(dataset2[user, :]) == length(dataset2[user, 1:end])
end

for item=1:Persa.items(dataset2)
@test length(dataset2[:, item]) == length(dataset2[1:end, item])
end

start = 2
last = Persa.users(dataset1) - 1

for item=1:Persa.items(dataset1)
elements = dataset1[start:last, item]
for element in elements
@test element[1] >= start && element[1] <= last
end
end

start = 2
last = Persa.items(dataset1) - 1

for user=1:Persa.users(dataset1)
elements = dataset1[user, start:last]
for element in elements
@test element[2] >= start && element[2] <= last
end
end

start = 2
last = Persa.users(dataset2) - 1

for item=1:Persa.items(dataset2)
elements = dataset2[start:last, item]
for element in elements
@test element[1] >= start && element[1] <= last
end
end

start = 2
last = Persa.items(dataset2) - 1

for user=1:Persa.users(dataset2)
elements = dataset2[user, start:last]
for element in elements
@test element[2] >= start && element[2] <= last
end
end

@test length(dataset1[1:end, 1:end]) == length(dataset1)
@test length(dataset2[1:end, 1:end]) == length(dataset2)
end

@testset "Matrix Conversion Tests" begin
matrix = Array(dataset1)

Expand Down
24 changes: 24 additions & 0 deletions test/learnTest.jl
@@ -0,0 +1,24 @@
struct RandomModel{T} <: Persa.Model{T}
preference::Persa.Preference{T}
users::Int
items::Int
end

RandomModel(dataset::Persa.Dataset) = RandomModel(dataset.preference, Persa.users(dataset), Persa.items(dataset))

@testset "Model Learn Tests" begin
df1 = createdummydatasetone()
dataset1 = Persa.Dataset(df1)

df2 = createdummydatasettwo()
dataset2 = Persa.Dataset(df2, 10, 10)

Persa.predict(model::RandomModel, user::Int, item::Int) = rand(model.preference.possibles)

model1 = RandomModel(dataset1)
model2 = RandomModel(dataset2)

@testset "Dummy Tests" begin
@test model1[1,1] > 0
end
end
48 changes: 48 additions & 0 deletions test/ratingTest.jl
Expand Up @@ -17,4 +17,52 @@

predict = Persa.PredictRating(4.5, preference)
label = Persa.Rating(4, preference)

rating = Persa.Rating(4, preference)
userPreference = Persa.UserPreference(1, 1, rating)
@test userPreference[1] == 1
@test userPreference[2] == 1
@test userPreference[3] == 4
@test isnan(userPreference[3]) == false
@test ismissing(userPreference[3]) == false

@test string(rating) == "4"

io = IOBuffer()
show(io, rating)
str = String(take!(io))
@test str == "Rating: 4"

@test string(userPreference) == "(user: 1, item: 1, rating: 4)"

rating = Persa.PredictRating(4.5, preference)
userPreference = Persa.UserPreference(2, 3, rating)
@test userPreference[1] == 2
@test userPreference[2] == 3
@test userPreference[3] == 4.5
@test !isnan(userPreference[3]) == true
@test !ismissing(userPreference[3]) == true

io = IOBuffer()
show(io, rating)
str = String(take!(io))
@test str == "Rating: 4.5 (4)"

@test string(rating) == "4.5 (4)"
@test string(userPreference) == "(user: 2, item: 3, rating: 4.5 (4))"

rating = Persa.MissingRating()
userPreference = Persa.UserPreference(2, 3, rating)
@test userPreference[1] == 2
@test userPreference[2] == 3
@test isnan(userPreference[3]) == true
@test ismissing(userPreference[3]) == true

io = IOBuffer()
show(io, rating)
str = String(take!(io))
@test str == "Rating: missing"

@test string(rating) == "missing"
@test string(userPreference) == "(user: 2, item: 3, rating: missing)"
end

0 comments on commit 1448322

Please sign in to comment.