Skip to content

Commit

Permalink
Merge pull request #75 from tomhanika/named-binary-csv-input
Browse files Browse the repository at this point in the history
Added Named Binary CSV Input and Doc
  • Loading branch information
tomhanika committed Jun 1, 2021
2 parents 234933a + 899913c commit 15176d4
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 18 deletions.
31 changes: 31 additions & 0 deletions doc/Common-FCA-File-Formats-for-Formal-Contexts.org
Expand Up @@ -166,3 +166,34 @@ XX
2
1 2
#+end_src
** Python Pandas
[[https://pandas.pydata.org/][Pandas]] is a python framework to manage tabular data structures and is
often used in data analysis. A common I/O format of conexp-clj and
pandas is CSV and Named-CSV.

*** Example
context
#+begin_src text
| 1 2
---+-----
a | . x
b | x x
#+end_src
Named-Binary-CSV format
#+begin_src text
objects,1,2
a,0,1
b,1,1
#+end_src

Conexp-clj I/O

#+BEGIN_SRC clojure
(write-context :named-binary-csv context "path/to/context.csv")
(read-context :named-binary-csv)
#+END_SRC

#+BEGIN_SRC python
context = pandas.read_table("path/to/context.csv",index_col=0,delimiter=",")
context.to_csv("path/to/context.csv")
#+END_SRC
68 changes: 54 additions & 14 deletions src/main/clojure/conexp/io/contexts.clj
Expand Up @@ -380,6 +380,10 @@
(when (or (empty? (objects ctx))
(empty? (attributes ctx)))
(unsupported-operation "Cannot export empty context in binary-csv format"))
(when (some (fn [x]
(and (string? x) (some #(= \, %) x)))
(concat (objects ctx) (attributes ctx)))
(unsupported-operation "Cannot export to :binary-csv format, object or attribute names contain \",\"."))
(let [objs (sort (objects ctx)),
atts (sort (attributes ctx))]
(with-out-writer file
Expand All @@ -395,29 +399,44 @@
(recur (rest atts))))
(println)))))

(add-context-input-format :named-binary-csv
(fn [rdr]
(= "NB" (subs (read-line) 0 2))))

(define-context-input-format :named-binary-csv
[file]
(with-in-reader file
"named binary CSV"
(let [[_ & atts] (split (read-line) #",")
atts-idx (reduce #(assoc %1 %2 (.indexOf atts %2)) {} atts)
[o & second-line] (split (read-line) #",")]
(loop [objs #{o},
incidence (set-of [o a] | a atts, :when (= (nth second-line (get atts-idx a)) "1"))]
(if-let [line (read-line)]
(let [[o & line] (split line #",")]
(recur (conj objs o)
(into incidence
(for [a atts :when (= (nth line (get atts-idx a)) "1")]
[o a]))))
(make-context objs atts incidence))))))

(define-context-output-format :named-binary-csv
[ctx file]
(when (or (empty? (objects ctx))
(empty? (attributes ctx)))
(unsupported-operation "Cannot export empty context in binary-csv format"))
(when (some (fn [x]
(and (string? x) (some #(= \, %) x)))
(concat (objects ctx) (attributes ctx)))
(unsupported-operation "Cannot export to :binary-csv format, object or attribute names contain \",\"."))
(let [objs (sort (objects ctx)),
atts (sort (attributes ctx))]
(with-out-writer file
(print "objects")
(doseq [m atts]
(print ", " m))
(println)
(println (clojure.string/join "," (into ["NB"] atts)))
(doseq [g objs]
(print g ",")
(loop [atts atts]
(when-let [m (first atts)]
(print (if (incident? ctx g m)
"1"
"0"))
(when (next atts)
(print ","))
(recur (rest atts))))
(println)))))
(println (clojure.string/join ","
(into [g]
(map #(if (incident? ctx g %) 1 0) atts))))))))


;; output as tex array
Expand Down Expand Up @@ -562,6 +581,27 @@
(catch javax.xml.stream.XMLStreamException _
(illegal-argument "Specified file does not contain valid XML."))))

(define-context-output-format :tex
[ctx file & options]
(let [{:keys [objorder attrorder]
:or {objorder (constantly true),
attrorder (constantly true)}} options]
(with-out-writer file
(println "\\begin{cxt}")
(println "\\cxtName{}")
(let [attr (sort-by attrorder (attributes ctx))
obj (sort-by objorder (objects ctx))]
(doseq [a attr]
(println (str "\\att{" a "}")))
(doseq [o obj]
(println (str "\\obj{"
(clojure.string/join ""
(for [a attr]
(if ((incidence ctx) [o a]) "x" ".")))
"}{" o "}")))
(println "\\end{cxt}")))))


;;; TODO

;; slf
Expand Down
7 changes: 4 additions & 3 deletions src/main/clojure/conexp/io/util.clj
Expand Up @@ -111,7 +111,8 @@

(defmulti ~write
~(str "Writes " name " to file using format.")
{:arglists (list [(symbol "format") (symbol ~name) (symbol "file")]
{:arglists (list [(symbol "format") (symbol ~name) (symbol "file") (symbol "& options")]
[(symbol "format") (symbol ~name) (symbol "file")]
[(symbol ~name) (symbol "file")])}
(fn [& args#]
(cond
Expand Down Expand Up @@ -161,9 +162,9 @@

(defmacro ~(symbol (str "define-" name "-output-format"))
~(str "Defines output format for " name "s.")
[~'input-format [~'thing ~'file] & ~'body]
[~'input-format [~'thing ~'file & ~'options] & ~'body]
`(defmethod ~'~write ~~'input-format
[~'~'_ ~~'thing ~~'file]
[~'~'_ ~~'thing ~~'file ~@~'options]
~@~'body))

nil)))
Expand Down
2 changes: 1 addition & 1 deletion src/test/clojure/conexp/io/util_test.clj
Expand Up @@ -25,7 +25,7 @@
(illegal-argument "out-in called with invalid type " type "."))
(let [tmp (.getAbsolutePath ^java.io.File (tmpfile))]
(@writer format object tmp)
(@reader tmp)))))
(@reader tmp format)))))

(defn out-in-out-in-test
"Checks for object of type type whether it passes out-in-out-in,
Expand Down

0 comments on commit 15176d4

Please sign in to comment.